The SEO Framework - Version 4.0.2

Version Description

France recently amended its copyright laws. In short, in France, it's now forbidden for content aggregators to display excerpts and previews of your content when no consent is given.

To accommodate those laws, Google will soon look for new directives, and we added new site-wide options to output those. These new options are disabled (unspecified) by default when you update The SEO Framework, but they are enabled (some access) by default when you install The SEO Framework on a new site. Please see this issue for our take on this.

In this update, we also fixed a few bugs and added various improvements.

Download this release

Release Info

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

Code changes from version 3.2.4 to 4.0.2

Files changed (81) hide show
  1. autodescription.php +35 -38
  2. bootstrap/activation.php +2 -23
  3. bootstrap/deactivation.php +2 -21
  4. bootstrap/define.php +48 -20
  5. bootstrap/envtest.php +14 -10
  6. bootstrap/load.php +53 -31
  7. bootstrap/upgrade.php +178 -34
  8. inc/classes/admin-init.class.php +313 -652
  9. inc/classes/admin-pages.class.php +525 -344
  10. inc/classes/bridges/index.php +9 -0
  11. inc/classes/bridges/listedit.class.php +269 -0
  12. inc/classes/bridges/listtable.class.php +290 -0
  13. inc/classes/bridges/ping.class.php +129 -0
  14. inc/classes/bridges/postsettings.class.php +200 -0
  15. inc/classes/bridges/scripts.class.php +1015 -0
  16. inc/classes/bridges/seobar.class.php +173 -0
  17. inc/classes/bridges/seosettings.class.php +746 -0
  18. inc/classes/bridges/sitemap.class.php +511 -0
  19. inc/classes/bridges/termsettings.class.php +70 -0
  20. inc/classes/builders/images.class.php +284 -0
  21. inc/classes/builders/index.php +6 -0
  22. inc/classes/builders/scripts.class.php +94 -32
  23. inc/classes/builders/seobar-page.class.php +986 -0
  24. inc/classes/builders/seobar-term.class.php +930 -0
  25. inc/classes/builders/seobar.class.php +227 -0
  26. inc/classes/builders/sitemap-base.class.php +484 -0
  27. inc/classes/builders/sitemap.class.php +204 -0
  28. inc/classes/cache.class.php +49 -80
  29. inc/classes/compat.class.php +0 -117
  30. inc/classes/core.class.php +181 -159
  31. inc/classes/debug.class.php +91 -96
  32. inc/classes/deprecated.class.php +622 -424
  33. inc/classes/detect.class.php +185 -215
  34. inc/classes/doing-it-right.class.php +0 -1814
  35. inc/classes/feed.class.php +18 -36
  36. inc/classes/generate-description.class.php +231 -85
  37. inc/classes/generate-image.class.php +408 -661
  38. inc/classes/generate-ldjson.class.php +73 -40
  39. inc/classes/generate-title.class.php +198 -119
  40. inc/classes/generate-url.class.php +181 -83
  41. inc/classes/generate.class.php +448 -115
  42. inc/classes/index.php +1 -11
  43. inc/classes/init.class.php +173 -138
  44. inc/classes/inpost.class.php +0 -409
  45. inc/classes/interpreters/index.php +7 -0
  46. inc/classes/interpreters/seobar.class.php +436 -0
  47. inc/classes/load.class.php +101 -86
  48. inc/classes/metaboxes.class.php +0 -656
  49. inc/classes/post-data.class.php +406 -267
  50. inc/classes/profile.class.php +39 -34
  51. inc/classes/query.class.php +236 -131
  52. inc/classes/render.class.php +94 -164
  53. inc/classes/sanitize.class.php +533 -197
  54. inc/classes/silencer.class.php +4 -2
  55. inc/classes/site-options.class.php +70 -69
  56. inc/classes/sitemaps.class.php +0 -1346
  57. inc/classes/term-data.class.php +288 -154
  58. inc/classes/user-data.class.php +18 -20
  59. inc/compat/index.php +10 -0
  60. inc/compat/php-mbstring.php +3 -0
  61. inc/compat/plugin-bbpress.php +36 -25
  62. inc/compat/plugin-buddypress.php +2 -0
  63. inc/compat/plugin-polylang.php +35 -5
  64. inc/compat/plugin-ultimatemember.php +20 -12
  65. inc/compat/plugin-woocommerce.php +136 -7
  66. inc/compat/plugin-wpforo.php +26 -29
  67. inc/compat/plugin-wpml.php +7 -5
  68. inc/compat/theme-genesis.php +8 -6
  69. inc/compat/wp-470.php +0 -10
  70. inc/functions/api.php +17 -4
  71. inc/functions/deprecated.php +4 -3
  72. inc/functions/index.php +6 -0
  73. inc/functions/upgrade-suggestion.php +21 -11
  74. inc/interfaces/debug.interface.php +0 -92
  75. inc/traits/core/index.php +6 -0
  76. inc/traits/core/overload.trait.php +136 -0
  77. inc/traits/index.php +14 -0
  78. inc/views/{metaboxes → admin/metaboxes}/description-metabox.php +6 -5
  79. inc/views/admin/metaboxes/feed-metabox.php +64 -0
  80. inc/views/{metaboxes → admin/metaboxes}/general-metabox.php +156 -108
  81. inc/views/{metaboxes → admin/metaboxes}/homepage-metabox.php +191 -299
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: 3.2.4
7
- * Author: Sybre Waaijer
8
  * Author URI: https://theseoframework.com/
9
  * License: GPLv3
10
  * Text Domain: autodescription
11
  * Domain Path: /language
 
 
12
  */
13
 
14
  defined( 'ABSPATH' ) or die;
@@ -33,18 +35,27 @@ defined( 'ABSPATH' ) or die;
33
  /**
34
  * @NOTE This file MUST be written according to WordPress' minimum PHP requirements.
35
  * Which is PHP 5.2.
 
 
36
  */
37
 
38
- //* Debug. Not to be used on production websites as it dumps and/or disables all kinds of stuff everywhere.
 
 
 
39
  // add_action( 'plugins_loaded', function() { if ( is_super_admin() ) {
40
- // if ( is_admin() ) {
41
- // define( 'THE_SEO_FRAMEWORK_DEBUG', true );
42
- // define( 'THE_SEO_FRAMEWORK_DISABLE_TRANSIENTS', true );
43
- // delete_option( 'the_seo_framework_upgraded_db_version' );
44
- // delete_option( 'the_seo_framework_tested_upgrade_version' );
45
- // add_filter( 'the_seo_framework_use_object_cache', '__return_false' );
46
- // }
 
 
 
47
  // }},0);
 
48
 
49
  /**
50
  * The plugin version.
@@ -53,7 +64,7 @@ defined( 'ABSPATH' ) or die;
53
  *
54
  * @since 2.3.5
55
  */
56
- define( 'THE_SEO_FRAMEWORK_VERSION', '3.2.4' );
57
 
58
  /**
59
  * The plugin Database version.
@@ -62,22 +73,25 @@ define( 'THE_SEO_FRAMEWORK_VERSION', '3.2.4' );
62
  *
63
  * @since 2.7.0
64
  */
65
- define( 'THE_SEO_FRAMEWORK_DB_VERSION', '3104' );
66
 
67
  /**
68
  * The plugin file, absolute unix path.
 
69
  * @since 2.2.9
70
  */
71
  define( 'THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE', __FILE__ );
72
 
73
  /**
74
  * The plugin's bootstrap folder location.
 
75
  * @since 3.1.0
76
  */
77
  define( 'THE_SEO_FRAMEWORK_BOOTSTRAP_PATH', dirname( THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE ) . DIRECTORY_SEPARATOR . 'bootstrap' . DIRECTORY_SEPARATOR );
78
 
79
  /**
80
  * Checks whether to start plugin or test the server environment first.
 
81
  * @since 2.8.0
82
  */
83
  if ( get_option( 'the_seo_framework_tested_upgrade_version' ) < THE_SEO_FRAMEWORK_DB_VERSION ) {
@@ -90,43 +104,26 @@ if ( get_option( 'the_seo_framework_tested_upgrade_version' ) < THE_SEO_FRAMEWOR
90
  }
91
 
92
  /**
93
- * Starts the plugin.
94
  *
95
  * @since 3.1.0
96
  * @access private
97
  */
98
  function the_seo_framework_boot() {
99
 
100
- /**
101
- * Defines environental constants.
102
- * @since 3.1.0
103
- */
104
  require THE_SEO_FRAMEWORK_BOOTSTRAP_PATH . 'define.php';
105
 
106
- /**
107
- * Load plugin API functions.
108
- * @since 3.1.0
109
- */
110
  require THE_SEO_FRAMEWORK_DIR_PATH_FUNCT . 'api.php';
111
 
112
- /**
113
- * Prepare plugin upgrader before the plugin loads.
114
- * @since 3.1.0
115
- * @since 3.1.2 Now performs a weak check.
116
- */
117
- if ( the_seo_framework_db_version() != THE_SEO_FRAMEWORK_DB_VERSION ) { // loose comparison OK.
118
- require THE_SEO_FRAMEWORK_BOOTSTRAP_PATH . 'upgrade.php';
119
- }
120
-
121
- /**
122
- * Load deprecated functions.
123
- * @since 3.1.0
124
- */
125
  require THE_SEO_FRAMEWORK_DIR_PATH_FUNCT . 'deprecated.php';
126
 
127
- /**
128
- * Load plugin.
129
- * @since 3.1.0
130
- */
131
  require THE_SEO_FRAMEWORK_BOOTSTRAP_PATH . 'load.php';
132
  }
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.0.2
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
  */
15
 
16
  defined( 'ABSPATH' ) or die;
35
  /**
36
  * @NOTE This file MUST be written according to WordPress' 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.4?+, it'll be PHP 7.1.
40
  */
41
 
42
+ // phpcs:disable, Squiz.Commenting.InlineComment, Squiz.PHP.CommentedOutCode
43
+ //
44
+ // Debug: Not to be used on production websites as it dumps and/or disables all kinds of stuff everywhere.
45
+ //
46
  // add_action( 'plugins_loaded', function() { if ( is_super_admin() ) {
47
+ // if ( is_admin() ) {
48
+ // define( 'THE_SEO_FRAMEWORK_DEBUG', true );
49
+ // define( 'THE_SEO_FRAMEWORK_DISABLE_TRANSIENTS', true );
50
+ // delete_option( 'the_seo_framework_upgraded_db_version' );
51
+ // ( $_GET['reset_tsf_upgrade'] ?? 0 ) and delete_option( 'the_seo_framework_upgraded_db_version' ) and delete_option( 'the_seo_framework_initial_db_version' );
52
+ // ( $_GET['downgrade_tsf'] ?? 0 ) and update_option( 'the_seo_framework_upgraded_db_version', (string) (int) $_GET['downgrade_tsf'] );
53
+ // ( $_GET['downgrade_tsf_initial'] ?? 0 ) and update_option( 'the_seo_framework_initial_db_version', (string) (int) $_GET['downgrade_tsf_initial'] );
54
+ // ( $_GET['reset_tsf_tested'] ?? 0 ) and delete_option( 'the_seo_framework_tested_upgrade_version' );
55
+ // add_filter( 'the_seo_framework_use_object_cache', '__return_false' );
56
+ // }
57
  // }},0);
58
+ // phpcs:enable, Squiz.Commenting.InlineComment, Squiz.PHP.CommentedOutCode
59
 
60
  /**
61
  * The plugin version.
64
  *
65
  * @since 2.3.5
66
  */
67
+ define( 'THE_SEO_FRAMEWORK_VERSION', '4.0.2' );
68
 
69
  /**
70
  * The plugin Database version.
73
  *
74
  * @since 2.7.0
75
  */
76
+ define( 'THE_SEO_FRAMEWORK_DB_VERSION', '4000' );
77
 
78
  /**
79
  * The plugin file, absolute unix path.
80
+ *
81
  * @since 2.2.9
82
  */
83
  define( 'THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE', __FILE__ );
84
 
85
  /**
86
  * The plugin's bootstrap folder location.
87
+ *
88
  * @since 3.1.0
89
  */
90
  define( 'THE_SEO_FRAMEWORK_BOOTSTRAP_PATH', dirname( THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE ) . DIRECTORY_SEPARATOR . 'bootstrap' . DIRECTORY_SEPARATOR );
91
 
92
  /**
93
  * Checks whether to start plugin or test the server environment first.
94
+ *
95
  * @since 2.8.0
96
  */
97
  if ( get_option( 'the_seo_framework_tested_upgrade_version' ) < THE_SEO_FRAMEWORK_DB_VERSION ) {
104
  }
105
 
106
  /**
107
+ * Starts the plugin, loads files outside of the global scope.
108
  *
109
  * @since 3.1.0
110
  * @access private
111
  */
112
  function the_seo_framework_boot() {
113
 
114
+ // Defines environental constants.
 
 
 
115
  require THE_SEO_FRAMEWORK_BOOTSTRAP_PATH . 'define.php';
116
 
117
+ // Load plugin API functions.
 
 
 
118
  require THE_SEO_FRAMEWORK_DIR_PATH_FUNCT . 'api.php';
119
 
120
+ // Prepare plugin upgrader before the plugin loads.
121
+ the_seo_framework_db_version() !== THE_SEO_FRAMEWORK_DB_VERSION
122
+ and require THE_SEO_FRAMEWORK_BOOTSTRAP_PATH . 'upgrade.php';
123
+
124
+ // Load deprecated functions.
 
 
 
 
 
 
 
 
125
  require THE_SEO_FRAMEWORK_DIR_PATH_FUNCT . 'deprecated.php';
126
 
127
+ // Load plugin.
 
 
 
128
  require THE_SEO_FRAMEWORK_BOOTSTRAP_PATH . 'load.php';
129
  }
bootstrap/activation.php CHANGED
@@ -1,8 +1,8 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework
4
- * @subpackage Bootstrap
5
  */
 
6
  namespace The_SEO_Framework\Bootstrap;
7
 
8
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -29,7 +29,6 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
29
  */
30
 
31
  //! @php7+ convert to IIFE
32
- _activation_setup_sitemap();
33
  _activation_set_options_autoload();
34
  _activation_set_plugin_check_caches();
35
 
@@ -50,26 +49,6 @@ function _activation_set_plugin_check_caches() {
50
  }
51
  }
52
 
53
- /**
54
- * Add and Flush rewrite rules on plugin activation.
55
- *
56
- * @since 2.6.6
57
- * @since 2.7.1: 1. Now no longer reinitializes global $wp_rewrite.
58
- * 2. Now always listens to the preconditions of the sitemap addition.
59
- * 3. Now flushes the rules on shutdown.
60
- * @since 2.8.0: Added namespace and renamed function.
61
- * @access private
62
- */
63
- function _activation_setup_sitemap() {
64
-
65
- $tsf = \the_seo_framework();
66
-
67
- if ( $tsf->loaded ) {
68
- $tsf->rewrite_rule_sitemap();
69
- \add_action( 'shutdown', 'flush_rewrite_rules' );
70
- }
71
- }
72
-
73
  /**
74
  * Turns on auto loading for The SEO Framework's main options.
75
  *
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Bootstrap\Install
 
4
  */
5
+
6
  namespace The_SEO_Framework\Bootstrap;
7
 
8
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
29
  */
30
 
31
  //! @php7+ convert to IIFE
 
32
  _activation_set_options_autoload();
33
  _activation_set_plugin_check_caches();
34
 
49
  }
50
  }
51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  /**
53
  * Turns on auto loading for The SEO Framework's main options.
54
  *
bootstrap/deactivation.php CHANGED
@@ -1,8 +1,8 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework
4
- * @subpackage Bootstrap
5
  */
 
6
  namespace The_SEO_Framework\Bootstrap;
7
 
8
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -30,25 +30,6 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
30
 
31
  //! @php7+ convert to IIFE
32
  _deactivation_unset_options_autoload();
33
- _deactivation_unset_sitemap();
34
-
35
- /**
36
- * Flush rewrite rules on plugin deactivation.
37
- *
38
- * @since 2.6.6
39
- * @since 2.7.1: 1. Now no longer reinitializes global $wp_rewrite.
40
- * 2. Now flushes the rules on shutdown.
41
- * @since 2.8.0: Added namespace and renamed function.
42
- * @access private
43
- * @global \WP_Rewrite $wp_rewrite
44
- */
45
- function _deactivation_unset_sitemap() {
46
-
47
- unset( $GLOBALS['wp_rewrite']->extra_rules_top['sitemap\.xml$'] );
48
- unset( $GLOBALS['wp_rewrite']->extra_rules_top['sitemap\.xsl$'] );
49
-
50
- \add_action( 'shutdown', 'flush_rewrite_rules' );
51
- }
52
 
53
  /**
54
  * Turns off autoloading for The SEO Framework main options.
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework/Bootstrap\Install
 
4
  */
5
+
6
  namespace The_SEO_Framework\Bootstrap;
7
 
8
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
30
 
31
  //! @php7+ convert to IIFE
32
  _deactivation_unset_options_autoload();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  /**
35
  * Turns off autoloading for The SEO Framework main options.
bootstrap/define.php CHANGED
@@ -1,10 +1,10 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework
4
- * @subpackage Bootstrap
5
- * No need to annotate namespacing here... there are only plain PHP queries.
6
  */
7
 
 
 
8
  defined( 'THE_SEO_FRAMEWORK_DB_VERSION' ) or die;
9
 
10
  /**
@@ -26,6 +26,7 @@ defined( 'THE_SEO_FRAMEWORK_DB_VERSION' ) or die;
26
 
27
  /**
28
  * Tells the world the plugin is present and to be used.
 
29
  * @since 3.1.0
30
  */
31
  define( 'THE_SEO_FRAMEWORK_PRESENT', true );
@@ -38,91 +39,118 @@ define( 'THE_SEO_FRAMEWORK_PRESENT', true );
38
  * @since 2.2.2
39
  * @param string THE_SEO_FRAMEWORK_SITE_OPTIONS
40
  */
41
- define( 'THE_SEO_FRAMEWORK_SITE_OPTIONS', (string) apply_filters( 'the_seo_framework_site_options', 'autodescription-site-settings' ) );
42
 
43
  /**
44
  * The plugin network options.
45
  *
46
- * Unused in the code.
47
  *
48
  * @since 2.2.2
49
  * @param string THE_SEO_FRAMEWORK_NETWORK_OPTIONS
50
  */
51
- define( 'THE_SEO_FRAMEWORK_NETWORK_OPTIONS', (string) apply_filters( 'the_seo_framework_network_settings', 'autodescription-network-settings' ) );
52
 
53
  /**
54
  * Plugin term options key.
 
55
  * @since 2.7.0
56
  * @param string THE_SEO_FRAMEWORK_TERM_OPTIONS
57
  */
58
- define( 'THE_SEO_FRAMEWORK_TERM_OPTIONS', (string) apply_filters( 'the_seo_framework_term_options', 'autodescription-term-settings' ) );
59
 
60
  /**
61
  * Plugin user term options key.
 
62
  * @since 2.7.0
63
  * @param string THE_SEO_FRAMEWORK_USER_OPTIONS
64
  */
65
- define( 'THE_SEO_FRAMEWORK_USER_OPTIONS', (string) apply_filters( 'the_seo_framework_user_options', 'autodescription-user-settings' ) );
66
 
67
  /**
68
  * Plugin updates cache key.
 
69
  * @since 3.1.0
70
  * @param string THE_SEO_FRAMEWORK_SITE_CACHE
71
  */
72
- define( 'THE_SEO_FRAMEWORK_SITE_CACHE', (string) apply_filters( 'the_seo_framework_site_cache', 'autodescription-updates-cache' ) );
73
 
74
  /**
75
- * The plugin map URL. Has a trailing slash.
76
  * Used for calling browser files.
 
77
  * @since 2.2.2
78
  */
79
- define( 'THE_SEO_FRAMEWORK_DIR_URL', plugin_dir_url( THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE ) );
80
 
81
  /**
82
  * The plugin file relative to the plugins dir. Does not have a trailing slash.
 
83
  * @since 2.2.8
84
  */
85
- define( 'THE_SEO_FRAMEWORK_PLUGIN_BASENAME', plugin_basename( THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE ) );
86
 
87
  /**
88
- * The plugin map absolute path.
89
- * Used for calling php files.
90
  * @since 2.2.2
91
  */
92
  define( 'THE_SEO_FRAMEWORK_DIR_PATH', dirname( THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE ) . DIRECTORY_SEPARATOR );
93
 
94
  /**
95
- * The plugin views map absolute path.
 
96
  * @since 2.7.0
97
  */
98
  define( 'THE_SEO_FRAMEWORK_DIR_PATH_VIEWS', THE_SEO_FRAMEWORK_DIR_PATH . 'inc' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR );
99
 
100
  /**
101
- * The plugin class map absolute path.
 
102
  * @since 2.2.9
103
  */
104
  define( 'THE_SEO_FRAMEWORK_DIR_PATH_CLASS', THE_SEO_FRAMEWORK_DIR_PATH . 'inc' . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR );
105
 
106
  /**
107
- * The plugin trait map absolute path.
 
108
  * @since 3.1.0
109
  */
110
  define( 'THE_SEO_FRAMEWORK_DIR_PATH_TRAIT', THE_SEO_FRAMEWORK_DIR_PATH . 'inc' . DIRECTORY_SEPARATOR . 'traits' . DIRECTORY_SEPARATOR );
111
 
112
  /**
113
- * The plugin interface map absolute path.
 
114
  * @since 2.8.0
115
  */
116
  define( 'THE_SEO_FRAMEWORK_DIR_PATH_INTERFACE', THE_SEO_FRAMEWORK_DIR_PATH . 'inc' . DIRECTORY_SEPARATOR . 'interfaces' . DIRECTORY_SEPARATOR );
117
 
118
  /**
119
- * The plugin function map absolute path.
 
120
  * @since 2.2.9
121
  */
122
  define( 'THE_SEO_FRAMEWORK_DIR_PATH_FUNCT', THE_SEO_FRAMEWORK_DIR_PATH . 'inc' . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR );
123
 
124
  /**
125
- * The plugin function map absolute path.
 
126
  * @since 2.8.0
127
  */
128
  define( 'THE_SEO_FRAMEWORK_DIR_PATH_COMPAT', THE_SEO_FRAMEWORK_DIR_PATH . 'inc' . DIRECTORY_SEPARATOR . 'compat' . DIRECTORY_SEPARATOR );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Bootstrap
 
 
4
  */
5
 
6
+ namespace The_SEO_Framework;
7
+
8
  defined( 'THE_SEO_FRAMEWORK_DB_VERSION' ) or die;
9
 
10
  /**
26
 
27
  /**
28
  * Tells the world the plugin is present and to be used.
29
+ *
30
  * @since 3.1.0
31
  */
32
  define( 'THE_SEO_FRAMEWORK_PRESENT', true );
39
  * @since 2.2.2
40
  * @param string THE_SEO_FRAMEWORK_SITE_OPTIONS
41
  */
42
+ define( 'THE_SEO_FRAMEWORK_SITE_OPTIONS', (string) \apply_filters( 'the_seo_framework_site_options', 'autodescription-site-settings' ) );
43
 
44
  /**
45
  * The plugin network options.
46
  *
47
+ * Unused in our code.
48
  *
49
  * @since 2.2.2
50
  * @param string THE_SEO_FRAMEWORK_NETWORK_OPTIONS
51
  */
52
+ define( 'THE_SEO_FRAMEWORK_NETWORK_OPTIONS', (string) \apply_filters( 'the_seo_framework_network_settings', 'autodescription-network-settings' ) );
53
 
54
  /**
55
  * Plugin term options key.
56
+ *
57
  * @since 2.7.0
58
  * @param string THE_SEO_FRAMEWORK_TERM_OPTIONS
59
  */
60
+ define( 'THE_SEO_FRAMEWORK_TERM_OPTIONS', (string) \apply_filters( 'the_seo_framework_term_options', 'autodescription-term-settings' ) );
61
 
62
  /**
63
  * Plugin user term options key.
64
+ *
65
  * @since 2.7.0
66
  * @param string THE_SEO_FRAMEWORK_USER_OPTIONS
67
  */
68
+ define( 'THE_SEO_FRAMEWORK_USER_OPTIONS', (string) \apply_filters( 'the_seo_framework_user_options', 'autodescription-user-settings' ) );
69
 
70
  /**
71
  * Plugin updates cache key.
72
+ *
73
  * @since 3.1.0
74
  * @param string THE_SEO_FRAMEWORK_SITE_CACHE
75
  */
76
+ define( 'THE_SEO_FRAMEWORK_SITE_CACHE', (string) \apply_filters( 'the_seo_framework_site_cache', 'autodescription-updates-cache' ) );
77
 
78
  /**
79
+ * The plugin folder URL. Has a trailing slash.
80
  * Used for calling browser files.
81
+ *
82
  * @since 2.2.2
83
  */
84
+ define( 'THE_SEO_FRAMEWORK_DIR_URL', \plugin_dir_url( THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE ) );
85
 
86
  /**
87
  * The plugin file relative to the plugins dir. Does not have a trailing slash.
88
+ *
89
  * @since 2.2.8
90
  */
91
+ define( 'THE_SEO_FRAMEWORK_PLUGIN_BASENAME', \plugin_basename( THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE ) );
92
 
93
  /**
94
+ * The plugin folder absolute path. Used for calling php files.
95
+ *
96
  * @since 2.2.2
97
  */
98
  define( 'THE_SEO_FRAMEWORK_DIR_PATH', dirname( THE_SEO_FRAMEWORK_PLUGIN_BASE_FILE ) . DIRECTORY_SEPARATOR );
99
 
100
  /**
101
+ * The plugin views folder absolute path.
102
+ *
103
  * @since 2.7.0
104
  */
105
  define( 'THE_SEO_FRAMEWORK_DIR_PATH_VIEWS', THE_SEO_FRAMEWORK_DIR_PATH . 'inc' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR );
106
 
107
  /**
108
+ * The plugin class folder absolute path.
109
+ *
110
  * @since 2.2.9
111
  */
112
  define( 'THE_SEO_FRAMEWORK_DIR_PATH_CLASS', THE_SEO_FRAMEWORK_DIR_PATH . 'inc' . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR );
113
 
114
  /**
115
+ * The plugin trait folder absolute path.
116
+ *
117
  * @since 3.1.0
118
  */
119
  define( 'THE_SEO_FRAMEWORK_DIR_PATH_TRAIT', THE_SEO_FRAMEWORK_DIR_PATH . 'inc' . DIRECTORY_SEPARATOR . 'traits' . DIRECTORY_SEPARATOR );
120
 
121
  /**
122
+ * The plugin interface folder absolute path.
123
+ *
124
  * @since 2.8.0
125
  */
126
  define( 'THE_SEO_FRAMEWORK_DIR_PATH_INTERFACE', THE_SEO_FRAMEWORK_DIR_PATH . 'inc' . DIRECTORY_SEPARATOR . 'interfaces' . DIRECTORY_SEPARATOR );
127
 
128
  /**
129
+ * The plugin function folder absolute path.
130
+ *
131
  * @since 2.2.9
132
  */
133
  define( 'THE_SEO_FRAMEWORK_DIR_PATH_FUNCT', THE_SEO_FRAMEWORK_DIR_PATH . 'inc' . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR );
134
 
135
  /**
136
+ * The plugin compatibility folder absolute path.
137
+ *
138
  * @since 2.8.0
139
  */
140
  define( 'THE_SEO_FRAMEWORK_DIR_PATH_COMPAT', THE_SEO_FRAMEWORK_DIR_PATH . 'inc' . DIRECTORY_SEPARATOR . 'compat' . DIRECTORY_SEPARATOR );
141
+
142
+ /**
143
+ * Robots setting, ignore protection.
144
+ *
145
+ * @since 4.0.0
146
+ * @see \The_SEO_Framework\Generate\robots_meta()
147
+ */
148
+ const ROBOTS_IGNORE_PROTECTION = 0b01;
149
+
150
+ /**
151
+ * Robots setting, ignore settings.
152
+ *
153
+ * @since 4.0.0
154
+ * @see \The_SEO_Framework\Generate\robots_meta()
155
+ */
156
+ const ROBOTS_IGNORE_SETTINGS = 0b10;
bootstrap/envtest.php CHANGED
@@ -1,10 +1,11 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework
4
- * @subpackage Bootstrap
5
  *
6
  * @NOTE This file MUST be written according to WordPress' minimum PHP requirements.
7
  * Which is PHP 5.2.
 
 
8
  */
9
 
10
  defined( 'THE_SEO_FRAMEWORK_DB_VERSION' ) or die;
@@ -69,15 +70,15 @@ function the_seo_framework_pre_boot_test() {
69
  }
70
 
71
  $requirements = array(
72
- 'php' => '50400',
73
  'wp' => '37965',
74
  );
75
 
76
- // phpcs:disable Generic.Formatting.MultipleStatementAlignment.NotSameWarning
77
- ! defined( 'PHP_VERSION_ID' ) || PHP_VERSION_ID < $requirements['php'] and $test = 1 // precision alignment ok.
78
  or $GLOBALS['wp_db_version'] < $requirements['wp'] and $test = 2
79
  or $test = true;
80
- // phpcs:enable Generic.Formatting.MultipleStatementAlignment.NotSameWarning
81
 
82
  //* All good.
83
  if ( true === $test ) {
@@ -108,7 +109,7 @@ function the_seo_framework_pre_boot_test() {
108
  switch ( $test ) :
109
  case 1:
110
  //* PHP requirements not met, always count up to encourage best standards.
111
- $requirement = 'PHP 5.4.0 or later';
112
  $issue = 'PHP version';
113
  $version = PHP_VERSION;
114
  $subtitle = 'Server Requirements';
@@ -116,7 +117,7 @@ function the_seo_framework_pre_boot_test() {
116
 
117
  case 2:
118
  //* WordPress requirements not met.
119
- $requirement = 'WordPress 4.6 or later';
120
  $issue = 'WordPress version';
121
  $version = $GLOBALS['wp_version'];
122
  $subtitle = 'WordPress Requirements';
@@ -137,9 +138,12 @@ function the_seo_framework_pre_boot_test() {
137
  sprintf(
138
  '<p><strong>The SEO Framework</strong> requires <em>%s</em>. Sorry about that!<br>Your %s is: <code>%s</code></p>
139
  <p>Do you want to <strong><a onclick="window.history.back()" href="%s">go back</a></strong>?</p>',
140
- esc_html( $requirement ), esc_html( $issue ), esc_html( $version ), esc_url( $pluginspage )
 
 
 
141
  ),
142
- sprintf( 'The SEO Framework &laquo; %s', esc_attr( $subtitle ) ),
143
  array( 'response' => intval( $response ) )
144
  );
145
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Bootstrap\Install
 
4
  *
5
  * @NOTE This file MUST be written according to WordPress' 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.4?+, it'll be PHP 7.1.
9
  */
10
 
11
  defined( 'THE_SEO_FRAMEWORK_DB_VERSION' ) or die;
70
  }
71
 
72
  $requirements = array(
73
+ 'php' => '50600',
74
  'wp' => '37965',
75
  );
76
 
77
+ // phpcs:disable, Generic.Formatting.MultipleStatementAlignment, WordPress.WhiteSpace.PrecisionAlignment
78
+ ! defined( 'PHP_VERSION_ID' ) || PHP_VERSION_ID < $requirements['php'] and $test = 1
79
  or $GLOBALS['wp_db_version'] < $requirements['wp'] and $test = 2
80
  or $test = true;
81
+ // phpcs:enable, Generic.Formatting.MultipleStatementAlignment, WordPress.WhiteSpace.PrecisionAlignment
82
 
83
  //* All good.
84
  if ( true === $test ) {
109
  switch ( $test ) :
110
  case 1:
111
  //* PHP requirements not met, always count up to encourage best standards.
112
+ $requirement = 'PHP 5.5.0 or later';
113
  $issue = 'PHP version';
114
  $version = PHP_VERSION;
115
  $subtitle = 'Server Requirements';
117
 
118
  case 2:
119
  //* WordPress requirements not met.
120
+ $requirement = 'WordPress 4.9 or later';
121
  $issue = 'WordPress version';
122
  $version = $GLOBALS['wp_version'];
123
  $subtitle = 'WordPress Requirements';
138
  sprintf(
139
  '<p><strong>The SEO Framework</strong> requires <em>%s</em>. Sorry about that!<br>Your %s is: <code>%s</code></p>
140
  <p>Do you want to <strong><a onclick="window.history.back()" href="%s">go back</a></strong>?</p>',
141
+ esc_html( $requirement ),
142
+ esc_html( $issue ),
143
+ esc_html( $version ),
144
+ esc_url( $pluginspage )
145
  ),
146
+ esc_attr( sprintf( 'The SEO Framework &laquo; %s', $subtitle ) ),
147
  array( 'response' => intval( $response ) )
148
  );
149
  }
bootstrap/load.php CHANGED
@@ -1,10 +1,8 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework
4
- * @subpackage Bootstrap
5
- * @TODO change namespace to The_SEO_Framework\Bootstrap
6
- * in a future major release.
7
  */
 
8
  namespace The_SEO_Framework;
9
 
10
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -28,9 +26,11 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
28
 
29
  \add_action( 'plugins_loaded', __NAMESPACE__ . '\\_init_locale', 4 );
30
  /**
31
- * Plugin locale 'autodescription'
32
  * Files located in plugin folder `../autodescription/language/`
 
33
  * @since 2.8.0
 
34
  */
35
  function _init_locale() {
36
  /**
@@ -39,10 +39,18 @@ function _init_locale() {
39
  \load_plugin_textdomain(
40
  'autodescription',
41
  false,
42
- THE_SEO_FRAMEWORK_DIR_PATH . 'language'
43
  );
44
  }
45
 
 
 
 
 
 
 
 
 
46
  \add_action( 'plugins_loaded', __NAMESPACE__ . '\\_init_tsf', 5 );
47
  /**
48
  * Load The_SEO_Framework_Load class
@@ -72,23 +80,27 @@ function _init_tsf() {
72
  */
73
  if ( \The_SEO_Framework\_can_load() ) {
74
  if ( \is_admin() ) {
75
- //! TODO: admin-only loader.
76
  $tsf = new \The_SEO_Framework\Load();
77
  $tsf->loaded = true;
78
 
 
 
79
  /**
80
- * Runs after TSF is loaded in the admin.
81
  * @since 3.1.0
 
82
  */
83
  \do_action( 'the_seo_framework_admin_loaded' );
84
  } else {
85
  $tsf = new \The_SEO_Framework\Load();
86
  $tsf->loaded = true;
 
 
87
  }
88
 
89
  /**
90
- * Runs after TSF is loaded.
91
  * @since 3.1.0
 
92
  */
93
  \do_action( 'the_seo_framework_loaded' );
94
  } else {
@@ -97,7 +109,7 @@ function _init_tsf() {
97
  }
98
 
99
  // did_action() checks for current action too.
100
- if ( false === \did_action( 'plugins_loaded' ) )
101
  $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' );
102
 
103
  return $tsf;
@@ -109,43 +121,53 @@ spl_autoload_register( __NAMESPACE__ . '\\_autoload_classes', true, true );
109
  * the plugin classes.
110
  *
111
  * @since 2.8.0
112
- * @since 3.1.0 1. No longer maintains cache.
113
- * 2. Now always returns void.
 
 
 
114
  * @uses THE_SEO_FRAMEWORK_DIR_PATH_CLASS
115
  * @access private
 
116
  *
117
  * @NOTE 'The_SEO_Framework\' is a reserved namespace. Using it outside of this
118
- * plugin's scope coul result in an error.
119
  *
120
  * @param string $class The class name.
121
  * @return void Early if the class is not within the current namespace.
122
  */
123
  function _autoload_classes( $class ) {
124
 
125
- if ( 0 !== strpos( $class, 'The_SEO_Framework\\', 0 ) )
126
- return;
127
-
128
- $strip = 'The_SEO_Framework\\';
129
 
130
- if ( strpos( $class, '_Interface' ) ) {
131
- $path = THE_SEO_FRAMEWORK_DIR_PATH_INTERFACE;
132
- $extension = '.interface.php';
133
- $class = str_replace( '_Interface', '', $class );
134
  } else {
135
- $path = THE_SEO_FRAMEWORK_DIR_PATH_CLASS;
136
- $extension = '.class.php';
137
 
138
- //: substr_count( $class, '\\', 2 ) >= 2 // strrpos... str_split...
139
- if ( 0 === strpos( $class, 'The_SEO_Framework\\Builders\\' ) ) {
140
- $path .= 'builders' . DIRECTORY_SEPARATOR;
141
- $strip .= 'Builders\\';
142
- }
 
 
 
143
  }
144
 
145
- $class = strtolower( str_replace( $strip, '', $class ) );
146
- $class = str_replace( '_', '-', $class );
147
 
148
- require $path . $class . $extension;
 
 
 
 
 
 
149
  }
150
 
151
  \add_action( 'activate_' . THE_SEO_FRAMEWORK_PLUGIN_BASENAME, __NAMESPACE__ . '\\_do_plugin_activation' );
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Bootstrap
 
 
 
4
  */
5
+
6
  namespace The_SEO_Framework;
7
 
8
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
26
 
27
  \add_action( 'plugins_loaded', __NAMESPACE__ . '\\_init_locale', 4 );
28
  /**
29
+ * Loads plugin locale 'autodescription'.
30
  * Files located in plugin folder `../autodescription/language/`
31
+ *
32
  * @since 2.8.0
33
+ * @since 4.0.2 Now points to the correct plugin folder for fallback MO-file loading (which was never used).
34
  */
35
  function _init_locale() {
36
  /**
39
  \load_plugin_textdomain(
40
  'autodescription',
41
  false,
42
+ dirname( THE_SEO_FRAMEWORK_PLUGIN_BASENAME ) . DIRECTORY_SEPARATOR . 'language'
43
  );
44
  }
45
 
46
+ /**
47
+ * Loads base overloading trait-collection.
48
+ *
49
+ * For now, other traits must be loaded via this function.
50
+ * However, we might deprecate this method in favor of an autoloader.
51
+ */
52
+ _load_trait( 'core/overload' );
53
+
54
  \add_action( 'plugins_loaded', __NAMESPACE__ . '\\_init_tsf', 5 );
55
  /**
56
  * Load The_SEO_Framework_Load class
80
  */
81
  if ( \The_SEO_Framework\_can_load() ) {
82
  if ( \is_admin() ) {
83
+ //! TODO: admin-only loader?
84
  $tsf = new \The_SEO_Framework\Load();
85
  $tsf->loaded = true;
86
 
87
+ $tsf->_load_early_compat_files();
88
+
89
  /**
 
90
  * @since 3.1.0
91
+ * Runs after TSF is loaded in the admin.
92
  */
93
  \do_action( 'the_seo_framework_admin_loaded' );
94
  } else {
95
  $tsf = new \The_SEO_Framework\Load();
96
  $tsf->loaded = true;
97
+
98
+ $tsf->_load_early_compat_files();
99
  }
100
 
101
  /**
 
102
  * @since 3.1.0
103
+ * Runs after TSF is loaded.
104
  */
105
  \do_action( 'the_seo_framework_loaded' );
106
  } else {
109
  }
110
 
111
  // did_action() checks for current action too.
112
+ if ( ! \did_action( 'plugins_loaded' ) )
113
  $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' );
114
 
115
  return $tsf;
121
  * the plugin classes.
122
  *
123
  * @since 2.8.0
124
+ * @since 3.1.0 : 1. No longer maintains cache.
125
+ * 2. Now always returns void.
126
+ * @since 4.0.0 : 1. Streamlined folder lookup by more effectively using the namespace.
127
+ * 2. Added timing functionality
128
+ * 3. No longer loads interfaces automatically.
129
  * @uses THE_SEO_FRAMEWORK_DIR_PATH_CLASS
130
  * @access private
131
+ * @staticvar bool $_timenow Whether to time this request. Used to prevent stacking timers during class extending.
132
  *
133
  * @NOTE 'The_SEO_Framework\' is a reserved namespace. Using it outside of this
134
+ * plugin's scope could result in an error.
135
  *
136
  * @param string $class The class name.
137
  * @return void Early if the class is not within the current namespace.
138
  */
139
  function _autoload_classes( $class ) {
140
 
141
+ if ( 0 !== strpos( $class, 'The_SEO_Framework\\', 0 ) ) return;
 
 
 
142
 
143
+ static $_timenow = true;
144
+ if ( $_timenow ) {
145
+ $_bootstrap_timer = microtime( true );
146
+ $_timenow = false;
147
  } else {
148
+ $_bootstrap_timer = 0;
149
+ }
150
 
151
+ $_chunks = explode( '\\', strtolower( $class ) );
152
+ $_chunck_count = count( $_chunks );
153
+
154
+ if ( $_chunck_count > 2 ) {
155
+ //? directory position = $_chunck_count - ( 2 = (The_SEO_Framework)\ + (Bridges/Builders/Interpreters)\ )
156
+ $rel_dir = implode( DIRECTORY_SEPARATOR, array_splice( $_chunks, 1, $_chunck_count - 2 ) ) . DIRECTORY_SEPARATOR;
157
+ } else {
158
+ $rel_dir = '';
159
  }
160
 
161
+ // The last part of the chunks is the class name--which corresponds to the file.
162
+ $file = str_replace( '_', '-', end( $_chunks ) );
163
 
164
+ // The extension is deemed to be ".class.php" always. We may wish to alter this for traits?
165
+ require THE_SEO_FRAMEWORK_DIR_PATH_CLASS . $rel_dir . $file . '.class.php';
166
+
167
+ if ( $_bootstrap_timer ) {
168
+ _bootstrap_timer( microtime( true ) - $_bootstrap_timer );
169
+ $_timenow = true;
170
+ }
171
  }
172
 
173
  \add_action( 'activate_' . THE_SEO_FRAMEWORK_PLUGIN_BASENAME, __NAMESPACE__ . '\\_do_plugin_activation' );
bootstrap/upgrade.php CHANGED
@@ -1,8 +1,8 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework
4
- * @subpackage Bootstrapp
5
  */
 
6
  namespace The_SEO_Framework\Bootstrap;
7
 
8
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -85,6 +85,11 @@ function _previous_db_version() {
85
  * 8. Now tries to increase memory limit. This probably isn't needed.
86
  * 9. Now runs on the front-end, too, via `init`, instead of `admin_init`.
87
  * @since 3.1.4 Now flushes object cache before the upgrade settings are called.
 
 
 
 
 
88
  */
89
  function _do_upgrade() {
90
 
@@ -92,20 +97,34 @@ function _do_upgrade() {
92
 
93
  if ( ! $tsf->loaded ) return;
94
 
 
 
95
  if ( $tsf->is_seo_settings_page( false ) ) {
96
- \wp_redirect( \self_admin_url() ); // phpcs:ignore -- self_admin_url() is safe.
 
97
  exit;
98
  }
99
 
 
 
 
 
 
 
 
100
  \wp_raise_memory_limit( 'tsf_upgrade' );
101
 
 
 
 
102
  /**
103
- * From WordPress' .../update-core.php
 
 
 
104
  * @since 3.1.4
105
  */
106
- // Clear the cache to prevent an update_option() from saving a stale database version to the cache
107
  \wp_cache_flush();
108
- // (Not all cache back ends listen to 'flush')
109
  \wp_cache_delete( 'alloptions', 'options' );
110
 
111
  $version = _previous_db_version();
@@ -147,17 +166,35 @@ function _do_upgrade() {
147
 
148
  //! From here, the upgrade procedures should be backward compatible.
149
  //? This means no data may be erased for at least 1 major version, or 1 year, whichever is later.
 
150
  if ( $version < '3103' ) {
151
  _do_upgrade_3103();
152
  $version = '3103';
153
  }
154
 
 
 
 
 
 
155
  /**
156
  * @since 2.7.0
157
  */
158
  \do_action( 'the_seo_framework_upgraded' );
159
  }
160
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  \add_action( 'the_seo_framework_upgraded', __NAMESPACE__ . '\\_upgrade_to_current' );
162
  /**
163
  * Upgrades the Database version to the latest version.
@@ -174,26 +211,73 @@ function _upgrade_to_current() {
174
  \update_option( 'the_seo_framework_upgraded_db_version', THE_SEO_FRAMEWORK_DB_VERSION );
175
 
176
  /**
177
- * From WordPress' .../update-core.php
 
 
 
178
  * @since 3.1.4
179
  */
180
- // Clear the cache to prevent a get_option() from retrieving a stale database version to the cache
181
  \wp_cache_flush();
182
- // (Not all cache back ends listen to 'flush')
183
  \wp_cache_delete( 'alloptions', 'options' );
184
  }
185
 
186
- \add_action( 'the_seo_framework_upgraded', __NAMESPACE__ . '\\_upgrade_reinitialize_rewrite', 99 );
187
  /**
188
- * Reinitializes the rewrite cache.
189
  *
190
- * This happens after the plugin's upgraded, because it's not critical, and when
191
- * this fails, the upgrader won't be locked.
 
 
 
 
 
 
192
  *
193
- * @since 3.1.2
194
  */
195
- function _upgrade_reinitialize_rewrite() {
196
- \the_seo_framework()->reinitialize_rewrite();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  }
198
 
199
  \add_action( 'the_seo_framework_upgraded', __NAMESPACE__ . '\\_prepare_upgrade_suggestion', 100 );
@@ -208,17 +292,22 @@ function _upgrade_reinitialize_rewrite() {
208
  * @return void Early when already enqueued
209
  */
210
  function _prepare_upgrade_suggestion() {
211
- static $run = false;
212
- if ( $run ) return;
213
-
214
- if ( \is_admin() ) {
215
- \add_action( 'admin_init', function() {
216
- if ( ! _previous_db_version() ) return;
217
- require THE_SEO_FRAMEWORK_DIR_PATH_FUNCT . 'upgrade-suggestion.php';
218
- }, 20 );
219
- }
 
 
 
 
 
220
 
221
- $run = true;
222
  }
223
 
224
  /**
@@ -228,7 +317,7 @@ function _prepare_upgrade_suggestion() {
228
  * @staticvar array $cache The cached notice strings.
229
  *
230
  * @param string $notice The upgrade notice.
231
- * @param bool $get Whether to return the upgrade notices.
232
  * @return array|void The notices when $get is true.
233
  */
234
  function _add_upgrade_notice( $notice = '', $get = false ) {
@@ -285,7 +374,7 @@ function _do_upgrade_2701() {
285
  \add_term_meta( $term_id, THE_SEO_FRAMEWORK_TERM_OPTIONS, $meta, true );
286
  }
287
 
288
- //= Rudimentary test for remaining ~300 users of the past passed, set initial version to 2600.
289
  \update_option( 'the_seo_framework_initial_db_version', '2600', 'no' );
290
  }
291
 
@@ -294,7 +383,6 @@ function _do_upgrade_2701() {
294
 
295
  /**
296
  * Removes term metadata for version 2802.
297
- * Reinitializes rewrite data for for sitemap stylesheet.
298
  *
299
  * @since 2.8.0
300
  */
@@ -394,8 +482,6 @@ function _do_upgrade_3060() {
394
  * Migrates `attachment_nofollow` option to post type settings.
395
  * Migrates `attachment_noarchive` option to post type settings.
396
  *
397
- * Loads suggestion for TSFEM.
398
- *
399
  * @since 3.1.0
400
  */
401
  function _do_upgrade_3103() {
@@ -411,14 +497,14 @@ function _do_upgrade_3103() {
411
 
412
  // Transport title separator.
413
  if ( isset( $defaults['title_separator'] ) )
414
- $tsf->update_option( 'title_separator', $tsf->get_option( 'title_seperator' ) ?: $defaults['title_separator'] );
415
 
416
  // Transport attachment_noindex, attachment_nofollow, and attachment_noarchive settings.
417
  foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
418
  $_option = $tsf->get_robots_post_type_option_id( $r );
419
  if ( isset( $defaults[ $_option ] ) ) {
420
- $_value = (array) ( $tsf->get_option( $_option ) ?: $defaults[ $_option ] );
421
- $_value['attachment'] = (int) (bool) $tsf->get_option( "attachment_$r" );
422
  $tsf->update_option( $_option, $_value );
423
  }
424
  }
@@ -442,3 +528,61 @@ function _do_upgrade_3103() {
442
 
443
  \update_option( 'the_seo_framework_upgraded_db_version', '3103' );
444
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Bootstrap\Install
 
4
  */
5
+
6
  namespace The_SEO_Framework\Bootstrap;
7
 
8
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
85
  * 8. Now tries to increase memory limit. This probably isn't needed.
86
  * 9. Now runs on the front-end, too, via `init`, instead of `admin_init`.
87
  * @since 3.1.4 Now flushes object cache before the upgrade settings are called.
88
+ * @since 4.0.0 1. Removed rewrite flushing; unless upgrading from <3300 to 3300
89
+ * 2. Added time limit.
90
+ * 3. No longer runs during AJAX.
91
+ * 4. Added an upgrading lock. Preventing upgrades running simultaneously.
92
+ * While this lock is active, the SEO Settings can't be accessed, either.
93
  */
94
  function _do_upgrade() {
95
 
97
 
98
  if ( ! $tsf->loaded ) return;
99
 
100
+ if ( \wp_doing_ajax() ) return;
101
+
102
  if ( $tsf->is_seo_settings_page( false ) ) {
103
+ // phpcs:ignore, WordPress.Security.SafeRedirect -- self_admin_url() is safe.
104
+ \wp_redirect( \self_admin_url() );
105
  exit;
106
  }
107
 
108
+ // Check if upgrade is locked. Otherwise, lock it.
109
+ if ( \get_transient( 'tsf_upgrade_lock' ) ) return;
110
+ \set_transient( 'tsf_upgrade_lock', true, 300 );
111
+
112
+ // Register this AFTER the transient is set. Otherwise, it may clear the transient in another thread.
113
+ register_shutdown_function( __NAMESPACE__ . '\\_release_upgrade_lock' );
114
+
115
  \wp_raise_memory_limit( 'tsf_upgrade' );
116
 
117
+ // phpcs:ignore, WordPress.PHP.NoSilencedErrors -- Feature may be disabled.
118
+ @set_time_limit( 300 );
119
+
120
  /**
121
+ * Clear the cache to prevent an update_option() from saving a stale database version to the cache.
122
+ * Not all caching plugins recognize 'flush', so delete the options cache too, just to be safe.
123
+ *
124
+ * @see WordPress' `.../update-core.php`
125
  * @since 3.1.4
126
  */
 
127
  \wp_cache_flush();
 
128
  \wp_cache_delete( 'alloptions', 'options' );
129
 
130
  $version = _previous_db_version();
166
 
167
  //! From here, the upgrade procedures should be backward compatible.
168
  //? This means no data may be erased for at least 1 major version, or 1 year, whichever is later.
169
+ //? We must manually delete settings that are no longer used; we merge them otherwise.
170
  if ( $version < '3103' ) {
171
  _do_upgrade_3103();
172
  $version = '3103';
173
  }
174
 
175
+ if ( $version < '3300' ) {
176
+ _do_upgrade_3300();
177
+ $version = '3300';
178
+ }
179
+
180
  /**
181
  * @since 2.7.0
182
  */
183
  \do_action( 'the_seo_framework_upgraded' );
184
  }
185
 
186
+ /**
187
+ * Releases the upgrade lock on shutdown.
188
+ *
189
+ * When the upgrader halts, timeouts, or crashes for any reason, this will run.
190
+ *
191
+ * @since 4.0.0
192
+ * @TODO add cache flush? @see _upgrade_to_current()
193
+ */
194
+ function _release_upgrade_lock() {
195
+ \delete_transient( 'tsf_upgrade_lock' );
196
+ }
197
+
198
  \add_action( 'the_seo_framework_upgraded', __NAMESPACE__ . '\\_upgrade_to_current' );
199
  /**
200
  * Upgrades the Database version to the latest version.
211
  \update_option( 'the_seo_framework_upgraded_db_version', THE_SEO_FRAMEWORK_DB_VERSION );
212
 
213
  /**
214
+ * Clear the cache to prevent a get_option() from retrieving a stale database version to the cache.
215
+ * Not all caching plugins recognize 'flush', so delete the options cache too, just to be safe.
216
+ *
217
+ * @see WordPress' `.../update-core.php`
218
  * @since 3.1.4
219
  */
 
220
  \wp_cache_flush();
 
221
  \wp_cache_delete( 'alloptions', 'options' );
222
  }
223
 
224
+ \add_action( 'the_seo_framework_upgraded', __NAMESPACE__ . '\\_prepare_upgrade_notice', 99 );
225
  /**
226
+ * Prepares a notice when the upgrade is completed.
227
  *
228
+ * @since 4.0.0
229
+ */
230
+ function _prepare_upgrade_notice() {
231
+ \add_action( 'admin_notices', __NAMESPACE__ . '\\_do_upgrade_notice' );
232
+ }
233
+
234
+ /**
235
+ * Outputs "your site has been upgraded" notification to applicable plugin users on upgrade.
236
  *
237
+ * @since 3.0.6
238
  */
239
+ function _do_upgrade_notice() {
240
+
241
+ if ( ! \current_user_can( 'update_plugins' ) ) return;
242
+
243
+ $tsf = \the_seo_framework();
244
+
245
+ if ( _previous_db_version() ) {
246
+ $tsf->do_dismissible_notice(
247
+ $tsf->convert_markdown(
248
+ sprintf(
249
+ /* translators: %s = Version number, surrounded in markdown-backticks. */
250
+ \esc_html__( 'Thank you for updating The SEO Framework! Your website has been upgraded successfully to use The SEO Framework at database version `%s`.', 'autodescription' ),
251
+ \esc_html( THE_SEO_FRAMEWORK_DB_VERSION )
252
+ ),
253
+ [ 'code' ]
254
+ ),
255
+ 'updated',
256
+ true,
257
+ false
258
+ );
259
+ } else {
260
+ $tsf->do_dismissible_notice(
261
+ \esc_html__( 'Thank you for installing The SEO Framework! Your website is now optimized for SEO, automatically. We hope you enjoy our free plugin. Good luck with your site!', 'autodescription' ),
262
+ 'updated',
263
+ false,
264
+ false
265
+ );
266
+ $tsf->do_dismissible_notice(
267
+ $tsf->convert_markdown(
268
+ sprintf(
269
+ /* translators: %s = Link, markdown. */
270
+ \esc_html__( "The SEO Framework only identifies itself rarely during plugin upgrades. We'd like to use this opportunity to highlight our [plugin setup guide](%s).", 'autodescription' ),
271
+ 'https://theseoframework.com/docs/seo-plugin-setup/' // Use https://tsf.fyi/docs/setup ? Needless redirection...
272
+ ),
273
+ [ 'a' ],
274
+ [ 'a_internal' => false ]
275
+ ),
276
+ 'updated',
277
+ false,
278
+ false
279
+ );
280
+ }
281
  }
282
 
283
  \add_action( 'the_seo_framework_upgraded', __NAMESPACE__ . '\\_prepare_upgrade_suggestion', 100 );
292
  * @return void Early when already enqueued
293
  */
294
  function _prepare_upgrade_suggestion() {
295
+ if ( ! \is_admin() ) return;
296
+ if ( ! _previous_db_version() ) return;
297
+
298
+ \add_action( 'admin_init', __NAMESPACE__ . '\\_include_upgrade_suggestion', 20 );
299
+ }
300
+
301
+ /**
302
+ * Loads plugin suggestion file
303
+ *
304
+ * @since 4.0.0
305
+ */
306
+ function _include_upgrade_suggestion() {
307
+
308
+ if ( \The_SEO_Framework\_has_run( __METHOD__ ) ) return;
309
 
310
+ require THE_SEO_FRAMEWORK_DIR_PATH_FUNCT . 'upgrade-suggestion.php';
311
  }
312
 
313
  /**
317
  * @staticvar array $cache The cached notice strings.
318
  *
319
  * @param string $notice The upgrade notice.
320
+ * @param bool $get Whether to return the upgrade notices.
321
  * @return array|void The notices when $get is true.
322
  */
323
  function _add_upgrade_notice( $notice = '', $get = false ) {
374
  \add_term_meta( $term_id, THE_SEO_FRAMEWORK_TERM_OPTIONS, $meta, true );
375
  }
376
 
377
+ //= Rudimentary test for remaining ~300 users of earlier versions passed, set initial version to 2600.
378
  \update_option( 'the_seo_framework_initial_db_version', '2600', 'no' );
379
  }
380
 
383
 
384
  /**
385
  * Removes term metadata for version 2802.
 
386
  *
387
  * @since 2.8.0
388
  */
482
  * Migrates `attachment_nofollow` option to post type settings.
483
  * Migrates `attachment_noarchive` option to post type settings.
484
  *
 
 
485
  * @since 3.1.0
486
  */
487
  function _do_upgrade_3103() {
497
 
498
  // Transport title separator.
499
  if ( isset( $defaults['title_separator'] ) )
500
+ $tsf->update_option( 'title_separator', $tsf->get_option( 'title_seperator', false ) ?: $defaults['title_separator'] );
501
 
502
  // Transport attachment_noindex, attachment_nofollow, and attachment_noarchive settings.
503
  foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
504
  $_option = $tsf->get_robots_post_type_option_id( $r );
505
  if ( isset( $defaults[ $_option ] ) ) {
506
+ $_value = (array) ( $tsf->get_option( $_option, false ) ?: $defaults[ $_option ] );
507
+ $_value['attachment'] = (int) (bool) $tsf->get_option( "attachment_$r", false );
508
  $tsf->update_option( $_option, $_value );
509
  }
510
  }
528
 
529
  \update_option( 'the_seo_framework_upgraded_db_version', '3103' );
530
  }
531
+
532
+ /**
533
+ * Flushes rewrite rules for one last time.
534
+ * Converts title separator's dash option to ndash.
535
+ * Enables pinging via cron.
536
+ * Flips the home_title_location option from left to right, and vice versa.
537
+ *
538
+ * Annotated as 3300, because 4.0 was supposed to be the 3.3 update before we
539
+ * refactored the whole API.
540
+ *
541
+ * @since 4.0.0
542
+ */
543
+ function _do_upgrade_3300() {
544
+
545
+ $tsf = \the_seo_framework();
546
+
547
+ if ( \get_option( 'the_seo_framework_initial_db_version' ) < '3300' ) {
548
+ // Remove old rewrite rules.
549
+ unset(
550
+ $GLOBALS['wp_rewrite']->extra_rules_top['sitemap\.xml$'],
551
+ $GLOBALS['wp_rewrite']->extra_rules_top['sitemap\.xsl$']
552
+ ); // redundant?
553
+ \add_action( 'shutdown', 'flush_rewrite_rules' );
554
+
555
+ $defaults = _upgrade_default_site_options();
556
+
557
+ // Convert 'dash' title option to 'ndash', silently. Nothing really changes for the user.
558
+ if ( 'dash' === $tsf->get_option( 'title_separator', false ) )
559
+ $tsf->update_option( 'title_separator', 'ndash' );
560
+
561
+ // Add default cron pinging option.
562
+ if ( isset( $defaults['ping_use_cron'] ) ) {
563
+ $tsf->update_option( 'ping_use_cron', $defaults['ping_use_cron'] );
564
+
565
+ if ( $defaults['ping_use_cron'] ) {
566
+ if ( $tsf->get_option( 'ping_google', false ) || $tsf->get_option( 'ping_bing', false ) ) {
567
+ _add_upgrade_notice(
568
+ \esc_html__( 'A cronjob is now used to ping search engines, and it alerts them to changes in your sitemap.', 'autodescription' )
569
+ );
570
+ }
571
+ }
572
+ }
573
+
574
+ // Flip the homepage title location to make it in line with all other titles.
575
+ $home_title_location = $tsf->get_option( 'home_title_location', false );
576
+ if ( 'left' === $home_title_location ) {
577
+ $tsf->update_option( 'home_title_location', 'right' );
578
+ } else {
579
+ $tsf->update_option( 'home_title_location', 'left' );
580
+ }
581
+
582
+ _add_upgrade_notice(
583
+ \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' )
584
+ );
585
+ }
586
+
587
+ \update_option( 'the_seo_framework_upgraded_db_version', '3300' );
588
+ }
inc/classes/admin-init.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -34,665 +36,211 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
34
  class Admin_Init extends Init {
35
 
36
  /**
37
- * Prepares scripts in the admin area.
38
  *
39
- * @since 3.1.0
40
  * @access private
41
- *
42
- * @param string|null $hook The current page hook.
43
  */
44
- public function _init_admin_scripts( $hook = null ) {
45
-
46
- $autoenqueue = false;
47
 
48
- if ( $this->is_seo_settings_page() ) {
49
- $autoenqueue = true;
50
- } else {
51
- $enqueue_hooks = [
52
- 'edit.php',
53
- 'post.php',
54
- 'post-new.php',
55
- 'edit-tags.php',
56
- 'term.php',
57
- ];
58
-
59
- if ( ! $this->get_option( 'display_seo_bar_tables' ) ) {
60
- $enqueue_hooks = array_diff( $enqueue_hooks, [ 'edit.php', 'edit-tags.php' ] );
61
- }
62
-
63
- if ( isset( $hook ) && $hook && in_array( $hook, $enqueue_hooks, true ) ) {
64
- if ( $this->post_type_supports_custom_seo() )
65
- $autoenqueue = true;
66
- }
67
  }
68
-
69
- $autoenqueue and $this->init_admin_scripts();
70
  }
71
 
72
  /**
73
- * Returns the static scripts class object.
74
- *
75
- * The first letter of the method is capitalized, to indicate it's a class caller.
76
- *
77
- * @since 3.1.0
78
- * @builder
79
  *
80
- * @return string The scripts class name.
 
81
  */
82
- public function Scripts() {
83
- // return Builder\Scripts::class; //= PHP 5.5+
84
- return '\\The_SEO_Framework\\Builders\\Scripts';
85
  }
86
 
87
  /**
88
- * Registers admin scripts and styles.
89
  *
90
- * @since 2.6.0
91
- * @since 3.1.0 First parameter is now deprecated.
92
  *
93
- * @param bool|null $dpr Deprecated.
94
- * @return void Early if already enqueued.
 
95
  */
96
- public function init_admin_scripts( $dpr = null ) {
97
 
98
- if ( null !== $dpr ) $this->_doing_it_wrong( __METHOD__, 'The first argument is deprecated. Use <code>the_seo_framework()->Scripts()::enqueue()</code> after calling this instead.', '3.1.0' );
99
-
100
- if ( _has_run( __METHOD__ ) ) return;
101
 
102
- //! PHP 5.4 compat: put in var.
103
- $scripts = $this->Scripts();
104
- $scripts::register( $this->get_default_scripts() );
105
 
106
- if ( $this->is_post_edit() ) {
107
- $this->enqueue_media_scripts();
108
- $this->enqueue_primaryterm_scripts();
109
 
110
- if ( $this->is_gutenberg_page() ) {
111
- $this->enqueue_gutenberg_compat_scripts();
112
- }
113
- } elseif ( $this->is_seo_settings_page() ) {
114
- $this->enqueue_media_scripts();
115
- \wp_enqueue_style( 'wp-color-picker' );
116
- \wp_enqueue_script( 'wp-color-picker' );
117
  }
118
- }
119
-
120
- /**
121
- * Returns a filterable sequential array of default scripts.
122
- *
123
- * @since 3.2.2
124
- *
125
- * @return array
126
- */
127
- public function get_default_scripts() {
128
- /**
129
- * @since 3.1.0
130
- * @param array $scripts The default CSS and JS loader settings.
131
- * @param string $scripts The \The_SEO_Framework\Builders\Scripts builder class name.
132
- */
133
- return (array) \apply_filters_ref_array( 'the_seo_framework_scripts', [
134
- [
135
- [
136
- 'id' => 'tsf',
137
- 'type' => 'css',
138
- 'deps' => [ 'tsf-tt' ],
139
- 'autoload' => true,
140
- 'hasrtl' => true,
141
- 'name' => 'tsf',
142
- 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
143
- 'ver' => THE_SEO_FRAMEWORK_VERSION,
144
- 'inline' => [
145
- '.tsf-flex-nav-tab .tsf-flex-nav-tab-radio:checked + .tsf-flex-nav-tab-label' => [
146
- 'box-shadow:0 -2px 0 0 {{$color_accent}} inset, 0 0 0 0 {{$color_accent}} inset',
147
- ],
148
- '.tsf-flex-nav-tab .tsf-flex-nav-tab-radio:focus + .tsf-flex-nav-tab-label:not(.tsf-no-focus-ring)' => [
149
- 'box-shadow:0 -2px 0 0 {{$color_accent}} inset, 0 0 0 1px {{$color_accent}} inset',
150
- ],
151
- ],
152
- ],
153
- [
154
- 'id' => 'tsf',
155
- 'type' => 'js',
156
- 'deps' => [ 'jquery', 'tsf-tt' ],
157
- 'autoload' => true,
158
- 'name' => 'tsf',
159
- 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
160
- 'ver' => THE_SEO_FRAMEWORK_VERSION,
161
- 'l10n' => [
162
- 'name' => 'tsfL10n',
163
- 'data' => $this->get_javascript_l10n(),
164
- ],
165
- ],
166
- [
167
- 'id' => 'tsf-tt',
168
- 'type' => 'css',
169
- 'deps' => [],
170
- 'autoload' => true,
171
- 'hasrtl' => false,
172
- 'name' => 'tt',
173
- 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
174
- 'ver' => THE_SEO_FRAMEWORK_VERSION,
175
- 'inline' => [
176
- '.tsf-tooltip-text-wrap' => [
177
- 'background-color:{{$bg_accent}}',
178
- 'color:{{$rel_bg_accent}}',
179
- ],
180
- '.tsf-tooltip-arrow:after' => [
181
- 'border-top-color:{{$bg_accent}}',
182
- ],
183
- '.tsf-tooltip-down .tsf-tooltip-arrow:after' => [
184
- 'border-bottom-color:{{$bg_accent}}',
185
- ],
186
- '.tsf-tooltip-text' => [
187
- \is_rtl() ? 'direction:rtl' : '',
188
- ],
189
- ],
190
- ],
191
- [
192
- 'id' => 'tsf-tt',
193
- 'type' => 'js',
194
- 'deps' => [ 'jquery' ],
195
- 'autoload' => true,
196
- 'name' => 'tt',
197
- 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
198
- 'ver' => THE_SEO_FRAMEWORK_VERSION,
199
- ],
200
- ],
201
- $this->Scripts(),
202
- ] );
203
- }
204
-
205
- /**
206
- * Enqueues Media Upload and Cropping scripts.
207
- *
208
- * @since 3.2.0
209
- * @staticvar bool|null $registered Prevents duplicate calls.
210
- */
211
- public function enqueue_gutenberg_compat_scripts() {
212
 
213
- if ( _has_run( __METHOD__ ) ) return;
214
-
215
- $scripts = $this->Scripts();
216
- $scripts::register( [
217
- [
218
- 'id' => 'tsf-gbc',
219
- 'type' => 'js',
220
- 'deps' => [ 'jquery', 'tsf', 'wp-editor', 'wp-data', 'lodash', 'react' ],
221
- 'autoload' => true,
222
- 'name' => 'tsf-gbc',
223
- 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
224
- 'ver' => THE_SEO_FRAMEWORK_VERSION,
225
- 'l10n' => [
226
- 'name' => 'tsfGBCL10n',
227
- 'data' => [],
228
- ],
229
- ],
230
- ] );
231
  }
232
 
233
  /**
234
- * Enqueues Media Upload and Cropping scripts.
235
- *
236
- * @since 3.1.0
237
- */
238
- public function enqueue_media_scripts() {
239
-
240
- if ( _has_run( __METHOD__ ) ) return;
241
-
242
- $args = [];
243
- if ( $this->is_post_edit() ) {
244
- $args['post'] = $this->get_the_real_admin_ID();
245
- }
246
- \wp_enqueue_media( $args );
247
-
248
- //! PHP 5.4 compat: put in var.
249
- $scripts = $this->Scripts();
250
- $scripts::register( [
251
- 'id' => 'tsf-media',
252
- 'type' => 'js',
253
- 'deps' => [ 'jquery', 'tsf' ],
254
- 'autoload' => true,
255
- 'name' => 'media',
256
- 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
257
- 'ver' => THE_SEO_FRAMEWORK_VERSION,
258
- 'l10n' => [
259
- 'name' => 'tsfMediaL10n',
260
- 'data' => [
261
- 'labels' => [
262
- 'social' => [
263
- 'imgSelect' => \esc_attr__( 'Select Image', 'autodescription' ),
264
- 'imgSelectTitle' => \esc_attr_x( 'Select social image', 'Button hover', 'autodescription' ),
265
- 'imgChange' => \esc_attr__( 'Change Image', 'autodescription' ),
266
- 'imgRemove' => \esc_attr__( 'Remove Image', 'autodescription' ),
267
- 'imgRemoveTitle' => \esc_attr__( 'Remove selected social image', 'autodescription' ),
268
- 'imgFrameTitle' => \esc_attr_x( 'Select Social Image', 'Frame title', 'autodescription' ),
269
- 'imgFrameButton' => \esc_attr__( 'Use this image', 'autodescription' ),
270
- ],
271
- 'logo' => [
272
- 'imgSelect' => \esc_attr__( 'Select Logo', 'autodescription' ),
273
- 'imgSelectTitle' => '',
274
- 'imgChange' => \esc_attr__( 'Change Logo', 'autodescription' ),
275
- 'imgRemove' => \esc_attr__( 'Remove Logo', 'autodescription' ),
276
- 'imgRemoveTitle' => \esc_attr__( 'Unset selected logo', 'autodescription' ),
277
- 'imgFrameTitle' => \esc_attr_x( 'Select Logo', 'Frame title', 'autodescription' ),
278
- 'imgFrameButton' => \esc_attr__( 'Use this image', 'autodescription' ),
279
- ],
280
- ],
281
- ],
282
- ],
283
- ] );
284
- }
285
-
286
- /**
287
- * Enqueues Primary Term Selection scripts.
288
  *
289
  * @since 3.1.0
 
 
290
  *
291
- * @return void Early if already enqueued.
292
- */
293
- public function enqueue_primaryterm_scripts() {
294
-
295
- if ( _has_run( __METHOD__ ) ) return;
296
-
297
- $id = $this->get_the_real_admin_ID();
298
-
299
- $post_type = \get_post_type( $id );
300
- $_taxonomies = $post_type ? $this->get_hierarchical_taxonomies_as( 'objects', $post_type ) : [];
301
- $taxonomies = [];
302
-
303
- $gutenberg = $this->is_gutenberg_page();
304
-
305
- foreach ( $_taxonomies as $_t ) {
306
- $singular_name = $this->get_tax_type_label( $_t->name );
307
-
308
- $taxonomies[ $_t->name ] = [
309
- 'name' => $_t->name,
310
- 'primary' => $this->get_primary_term_id( $id, $_t->name ) ?: 0,
311
- ] + (
312
- $gutenberg ? [
313
- 'i18n' => [
314
- /* translators: %s = term name */
315
- 'selectPrimary' => sprintf( \esc_html__( 'Select Primary %s', 'autodescription' ), $singular_name ),
316
- ],
317
- ] : [
318
- 'i18n' => [
319
- /* translators: %s = term name */
320
- 'makePrimary' => sprintf( \esc_html__( 'Make primary %s', 'autodescription' ), strtolower( $singular_name ) ),
321
- /* translators: %s = term name */
322
- 'primary' => sprintf( \esc_html__( 'Primary %s', 'autodescription' ), strtolower( $singular_name ) ),
323
- 'name' => strtolower( $singular_name ),
324
- ],
325
- ]
326
- );
327
- }
328
-
329
- $inline_css = [];
330
- if ( \is_rtl() ) {
331
- $inline_css = [
332
- '.tsf-primary-term-selector' => [
333
- 'float:left;',
334
- ],
335
- '.tsf-primary-term-selector-help-wrap' => [
336
- 'left:25px;',
337
- 'right:initial;',
338
- ],
339
- ];
340
- }
341
-
342
- if ( $gutenberg ) {
343
- $vars = [
344
- 'id' => 'tsf-pt-gb',
345
- 'name' => 'pt-gb',
346
- ];
347
- $deps = [ 'jquery', 'tsf', 'wp-hooks', 'wp-element', 'wp-components', 'wp-url', 'wp-api-fetch', 'lodash', 'react' ];
348
- } else {
349
- $vars = [
350
- 'id' => 'tsf-pt',
351
- 'name' => 'pt',
352
- ];
353
- $deps = [ 'jquery', 'tsf', 'tsf-tt' ];
354
- }
355
-
356
- //! PHP 5.4 compat: put in var.
357
- $scripts = $this->Scripts();
358
- $scripts::register( [
359
- [
360
- 'id' => $vars['id'],
361
- 'type' => 'js',
362
- 'deps' => $deps,
363
- 'autoload' => true,
364
- 'name' => $vars['name'],
365
- 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
366
- 'ver' => THE_SEO_FRAMEWORK_VERSION,
367
- 'l10n' => [
368
- 'name' => 'tsfPTL10n',
369
- 'data' => [
370
- 'taxonomies' => $taxonomies,
371
- ],
372
- ],
373
- 'tmpl' => [
374
- 'file' => $this->get_view_location( 'templates/inpost/primary-term-selector' ),
375
- ],
376
- ],
377
- [
378
- 'id' => 'tsf-pt',
379
- 'type' => 'css',
380
- 'deps' => [ 'tsf-tt' ],
381
- 'autoload' => true,
382
- 'hasrtl' => false,
383
- 'name' => 'pt',
384
- 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
385
- 'ver' => THE_SEO_FRAMEWORK_VERSION,
386
- 'inline' => $inline_css,
387
- ],
388
- ] );
389
- }
390
-
391
- /**
392
- * Generate Javascript Localization.
393
- *
394
- * @TODO rewrite, it's slow and a mess.
395
- *
396
- * @since 2.6.0
397
- * @staticvar array $strings : The l10n strings.
398
- * @since 2.7.0 Added AJAX nonce: 'autodescription-ajax-nonce'
399
- * @since 2.8.0 1 : Added input detection: 'hasInput'
400
- * 2 : Reworked output.
401
- * 3 : Removed unused caching.
402
- * 4 : Added dynamic output control.
403
- * @since 2.9.0 Added boolean $returnValue['states']['isSettingsPage']
404
- * @since 3.0.4 `descPixelGuideline` has been increased from "920 and 820" to "1820 and 1720" respectively.
405
- * @since 3.2.2 Added string $returnValue['nonces']['manage_options']
406
- *
407
- * @return array $strings The l10n strings.
408
  */
409
- protected function get_javascript_l10n() {
410
-
411
- $id = $this->get_the_real_ID();
412
-
413
- $default_title = '';
414
- $title_additions = '';
415
 
416
- $use_title_additions = $this->use_title_branding();
417
- $home_tagline = $this->get_option( 'homepage_title_tagline' );
418
- $title_location = $this->get_option( 'title_location' );
419
- $title_separator = \esc_html( $this->get_separator( 'title' ) );
420
 
421
- $is_home = false;
422
- $is_settings_page = $this->is_seo_settings_page();
423
- $page_on_front = $this->has_page_on_front();
424
- $is_post_edit = $this->is_post_edit();
425
- $is_term_edit = $this->is_term_edit();
426
- $has_input = $is_settings_page || $is_post_edit || $is_term_edit;
427
 
428
- $_decode_flags = ENT_QUOTES | ENT_COMPAT;
429
 
430
- if ( $is_settings_page ) {
431
- // We're on our SEO settings pages.
432
- if ( $page_on_front ) {
433
- // Home is a page.
434
- $id = (int) \get_option( 'page_on_front' );
435
- $inpost_title = $this->get_custom_field( '_genesis_title', $id );
436
  } else {
437
- // Home is a blog.
438
- $inpost_title = '';
439
  }
440
- $default_title = $inpost_title ?: $this->get_blogname();
441
- $title_additions = $this->get_home_page_tagline();
442
 
443
- $use_title_additions = (bool) $this->get_option( 'homepage_tagline' );
444
- } else {
445
- // We're somewhere within default WordPress pages.
446
- if ( $is_term_edit ) {
447
- //* Category or Tag.
448
- if ( $this->get_current_taxonomy() && $id ) {
449
- // DEBUG: Use get_generated_archive_title() instead...? use_generated_archive_prefix() is in the way.
450
- $default_title = $this->get_generated_single_term_title( $this->fetch_the_term( $id ) );
451
- $title_additions = $this->get_blogname();
452
- }
453
- } elseif ( $this->is_static_frontpage( $id ) ) { // implies $is_post_edit or $is_settings_page
454
- $default_title = $this->get_option( 'homepage_title' ) ?: $this->get_blogname();
455
- $title_location = $this->get_option( 'home_title_location' );
456
-
457
- $is_home = true;
458
-
459
- $use_title_additions = (bool) $this->get_option( 'homepage_tagline' );
460
- $title_additions = $this->get_home_page_tagline();
461
- } elseif ( $is_post_edit ) {
462
- $default_title = $this->get_raw_generated_title( [ 'id' => $id ] );
463
- $title_additions = $this->get_blogname();
464
- } else {
465
- //* We're in a special place.
466
- // Can't fetch title.
467
- $default_title = '';
468
- $title_additions = $this->get_blogname();
469
- }
470
- }
471
-
472
- $this->set_js_nonces( [
473
- /**
474
- * Use $this->get_settings_capability() ?... might conflict with other nonces.
475
- * @augments tsfMedia 'upload_files'
476
- */
477
- 'manage_options' => \current_user_can( 'manage_options' ) ? \wp_create_nonce( 'tsf-ajax-manage_options' ) : false,
478
- 'upload_files' => \current_user_can( 'upload_files' ) ? \wp_create_nonce( 'tsf-ajax-upload_files' ) : false,
479
- 'edit_posts' => \current_user_can( 'edit_posts' ) ? \wp_create_nonce( 'tsf-ajax-edit_posts' ) : false,
480
- ] );
481
-
482
- $term_name = '';
483
- $use_term_prefix = false;
484
- if ( $is_term_edit ) {
485
- $term_name = $this->get_tax_type_label( $this->get_current_taxonomy(), true );
486
- $use_term_prefix = $this->use_generated_archive_prefix();
487
- }
488
-
489
- $social_settings_locks = [];
490
-
491
- if ( $page_on_front ) {
492
- if ( $is_settings_page ) {
493
- // PH = placeholder
494
- $social_settings_locks = [
495
- 'ogTitlePHLock' => (bool) $this->get_custom_field( '_open_graph_title', $id ),
496
- 'ogDescriptionPHLock' => (bool) $this->get_custom_field( '_open_graph_description', $id ),
497
- 'twTitlePHLock' => (bool) $this->get_custom_field( '_twitter_title', $id ),
498
- 'twDescriptionPHLock' => (bool) $this->get_custom_field( '_twitter_description', $id ),
499
- ];
500
- } elseif ( $is_home ) {
501
- $social_settings_locks = [
502
- 'refTitleLock' => (bool) $this->get_option( 'homepage_title' ),
503
- 'refDescriptionLock' => (bool) $this->get_option( 'homepage_description' ),
504
- 'ogTitleLock' => (bool) $this->get_option( 'homepage_og_title' ),
505
- 'ogDescriptionLock' => (bool) $this->get_option( 'homepage_og_description' ),
506
- 'twTitleLock' => (bool) $this->get_option( 'homepage_twitter_title' ),
507
- 'twDescriptionLock' => (bool) $this->get_option( 'homepage_twitter_description' ),
508
  ];
509
- }
510
- }
511
 
512
- $social_settings_placeholders = [];
513
-
514
- if ( $is_post_edit || $is_settings_page ) {
515
- if ( $is_settings_page ) {
516
- if ( $page_on_front ) {
517
- $social_settings_placeholders = [
518
- 'ogDesc' => $this->get_custom_field( '_genesis_description', $id ) ?: $this->get_generated_open_graph_description( [ 'id' => $id ] ),
519
- 'twDesc' => $this->get_custom_field( '_genesis_description', $id ) ?: $this->get_generated_twitter_description( [ 'id' => $id ] ),
520
- ];
521
- } else {
522
- $social_settings_placeholders = [
523
- 'ogDesc' => $this->get_generated_open_graph_description( [ 'id' => $id ] ),
524
- 'twDesc' => $this->get_generated_twitter_description( [ 'id' => $id ] ),
525
- ];
526
  }
527
- } elseif ( $is_home ) {
528
- $social_settings_placeholders = [
529
- 'ogDesc' => $this->get_option( 'homepage_description' ) ?: $this->get_generated_open_graph_description( [ 'id' => $id ] ),
530
- 'twDesc' => $this->get_option( 'homepage_description' ) ?: $this->get_generated_twitter_description( [ 'id' => $id ] ),
531
- ];
532
- } else {
533
- $social_settings_placeholders = [
534
- 'ogDesc' => $this->get_generated_open_graph_description( [ 'id' => $id ] ),
535
- 'twDesc' => $this->get_generated_twitter_description( [ 'id' => $id ] ),
536
- ];
537
- }
538
-
539
- foreach ( $social_settings_placeholders as &$v ) {
540
- $v = html_entity_decode( $v, $_decode_flags, 'UTF-8' );
541
  }
542
- }
543
 
544
- $input_guidelines = [];
545
- $input_guidelines_i18n = [];
546
- if ( $has_input ) {
547
- $input_guidelines = $this->get_input_guidelines();
548
- $input_guidelines_i18n = $this->get_input_guidelines_i18n();
549
  }
550
 
551
- $l10n = [
552
- 'nonces' => $this->get_js_nonces(),
553
- 'states' => [
554
- 'isRTL' => (bool) \is_rtl(),
555
- 'isHome' => $is_home,
556
- 'hasInput' => $has_input,
557
- 'counterType' => \absint( $this->get_user_option( 0, 'counter_type', 3 ) ),
558
- 'useTagline' => $use_title_additions,
559
- 'taglineLocked' => (bool) $this->get_option( 'title_rem_additions' ),
560
- 'useTermPrefix' => $use_term_prefix,
561
- 'isSettingsPage' => $is_settings_page,
562
- 'isPostEdit' => $is_post_edit,
563
- 'isTermEdit' => $is_term_edit,
564
- 'postType' => $is_post_edit ? \get_post_type( $id ) : false,
565
- 'isPrivate' => $has_input && $is_post_edit && $id && $this->is_private( $id ),
566
- 'isPasswordProtected' => $has_input && $is_post_edit && $id && $this->is_password_protected( $id ),
567
- 'debug' => $this->script_debug,
568
- 'homeLocks' => $social_settings_locks,
569
- 'stripTitleTags' => (bool) $this->get_option( 'title_strip_tags' ),
570
- 'isGutenbergPage' => $this->is_gutenberg_page(),
571
- ],
572
- 'i18n' => [
573
- 'saveAlert' => \__( 'The changes you made will be lost if you navigate away from this page.', 'autodescription' ),
574
- 'confirmReset' => \__( 'Are you sure you want to reset all SEO settings to their defaults?', 'autodescription' ),
575
- // phpcs:ignore -- WordPress doesn't have a comment, either.
576
- 'privateTitle' => $has_input && $id ? trim( str_replace( '%s', '', \__( 'Private: %s', 'default' ) ) ) : '',
577
- // phpcs:ignore -- WordPress doesn't have a comment, either.
578
- 'protectedTitle' => $has_input && $id ? trim( str_replace( '%s', '', \__( 'Protected: %s', 'default' ) ) ) : '',
579
- /* translators: Pixel counter. 1: width, 2: guideline */
580
- 'pixelsUsed' => $has_input ? \__( '%1$d out of %2$d pixels are used.', 'autodescription' ) : '',
581
- 'inputGuidelines' => $input_guidelines_i18n,
582
- ],
583
- 'params' => [
584
- 'objectTitle' => $this->s_title_raw( $default_title ),
585
- 'defaultTitle' => $this->s_title_raw( $default_title ),
586
- 'titleAdditions' => $this->s_title_raw( $title_additions ),
587
- 'blogDescription' => $this->s_title_raw( $this->get_blogdescription() ),
588
- 'termName' => $this->s_title_raw( $term_name ),
589
- 'untitledTitle' => $this->s_title_raw( $this->get_static_untitled_title() ),
590
- 'titleSeparator' => $title_separator,
591
- 'titleLocation' => $title_location,
592
- 'inputGuidelines' => $input_guidelines,
593
- 'socialPlaceholders' => $social_settings_placeholders,
594
- ],
595
- ];
596
-
597
- foreach ( [ 'i18n', 'params' ] as $key ) {
598
- foreach ( $l10n[ $key ] as &$v ) {
599
- if ( is_scalar( $v ) )
600
- $v = html_entity_decode( $v, $_decode_flags, 'UTF-8' );
601
- }
602
- }
603
-
604
- /**
605
- * @since 3.0.0
606
- * @param array $l10n The JS l10n values.
607
- */
608
- return (array) \apply_filters( 'the_seo_framework_js_l10n', $l10n );
609
- }
610
-
611
- /**
612
- * Sets up additional JS l10n values for nonces.
613
- *
614
- * They are put under object 'tsfemL10n.nonces[ $key ] = $val'.
615
- *
616
- * @since 2.9.0
617
- * @access private
618
- *
619
- * @param string|array $key Required. The object key or array of keys and values. Requires escape.
620
- * @param mixed $val The object value if $key is string. Requires escape.
621
- */
622
- public function set_js_nonces( $key, $val = null ) {
623
- $this->get_js_nonces( $key, $val, false );
624
  }
625
 
626
  /**
627
- * Maintains and Returns additional JS l10n.
628
- *
629
- * They are put under object 'tsfemL10n.nonces[ $key ] = $val'.
630
- *
631
- * If $key is an array, $val is ignored and $key's values are used instead.
632
  *
633
- * @since 2.9.0
634
- * @access private
635
- * @staticvar object $nonces The cached nonces object.
636
  *
637
- * @param string|array $key The object key or array of keys and values. Requires escape.
638
- * @param mixed $val The object value if $key is string. Requires escape.
639
- * @param bool $get Whether to return the cached nonces.
640
- * @return object Early when $get is true
641
  */
642
- public function get_js_nonces( $key = null, $val = null, $get = true ) {
643
-
644
- static $nonces = null;
645
-
646
- if ( null === $nonces )
647
- $nonces = new \stdClass();
648
 
649
- if ( $get )
650
- return $nonces;
651
 
652
- if ( is_string( $key ) ) {
653
- $nonces->$key = $val;
654
- } elseif ( is_array( $key ) ) {
655
- foreach ( $key as $k => $v ) {
656
- $nonces->$k = $v;
657
- }
658
- }
659
  }
660
 
661
  /**
662
  * Returns the title and description input guideline table, for
663
  * (Google) search, Open Graph, and Twitter.
664
  *
 
 
 
 
 
 
665
  * @since 3.1.0
 
 
666
  * @staticvar array $guidelines
667
  * @TODO Consider splitting up search into Google, Bing, etc., as we might
668
  * want users to set their preferred search engine. Now, these engines
669
  * are barely any different.
 
670
  *
 
671
  * @return array
672
  */
673
- public function get_input_guidelines() {
674
- static $guidelines;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
675
  /**
676
  * @since 3.1.0
677
  * @param array $guidelines The title and description guidelines.
678
  * Don't alter the format. Only change the numeric values.
679
  */
680
- return isset( $guidelines ) ? $guidelines : $guidelines = (array) \apply_filters(
681
  'the_seo_framework_input_guidelines',
682
  [
683
  'title' => [
684
  'search' => [
685
  'chars' => [
686
- 'lower' => 25,
687
- 'goodLower' => 35,
688
- 'goodUpper' => 65,
689
- 'upper' => 75,
690
  ],
691
  'pixels' => [
692
- 'lower' => 200,
693
- 'goodLower' => 280,
694
- 'goodUpper' => 520,
695
- 'upper' => 600,
696
  ],
697
  ],
698
  'opengraph' => [
@@ -717,16 +265,16 @@ class Admin_Init extends Init {
717
  'description' => [
718
  'search' => [
719
  'chars' => [
720
- 'lower' => 45,
721
- 'goodLower' => 80,
722
- 'goodUpper' => 160,
723
- 'upper' => 320,
724
  ],
725
  'pixels' => [
726
- 'lower' => 256,
727
- 'goodLower' => 455,
728
- 'goodUpper' => 910,
729
- 'upper' => 1820,
730
  ],
731
  ],
732
  'opengraph' => [
@@ -750,6 +298,7 @@ class Admin_Init extends Init {
750
  ],
751
  ]
752
  );
 
753
  }
754
 
755
  /**
@@ -758,12 +307,13 @@ class Admin_Init extends Init {
758
  * Already attribute-escaped.
759
  *
760
  * @since 3.1.0
 
761
  *
762
  * @return array
763
  */
764
  public function get_input_guidelines_i18n() {
765
  return [
766
- 'long' => [
767
  'empty' => \esc_attr__( "There's no content.", 'autodescription' ),
768
  'farTooShort' => \esc_attr__( "It's too short and it should have more information.", 'autodescription' ),
769
  'tooShort' => \esc_attr__( "It's short and it could have more information.", 'autodescription' ),
@@ -771,7 +321,7 @@ class Admin_Init extends Init {
771
  'farTooLong' => \esc_attr__( "It's too long and it will get truncated in search.", 'autodescription' ),
772
  'good' => \esc_attr__( 'Length is good.', 'autodescription' ),
773
  ],
774
- 'short' => [
775
  'empty' => \esc_attr_x( 'Empty', 'The string is empty', 'autodescription' ),
776
  'farTooShort' => \esc_attr__( 'Far too short', 'autodescription' ),
777
  'tooShort' => \esc_attr__( 'Too short', 'autodescription' ),
@@ -779,6 +329,14 @@ class Admin_Init extends Init {
779
  'farTooLong' => \esc_attr__( 'Far too long', 'autodescription' ),
780
  'good' => \esc_attr__( 'Good', 'autodescription' ),
781
  ],
 
 
 
 
 
 
 
 
782
  ];
783
  }
784
 
@@ -793,6 +351,7 @@ class Admin_Init extends Init {
793
  * @uses WP Core check_ajax_referer()
794
  * @see @link https://developer.wordpress.org/reference/functions/check_ajax_referer/
795
  *
 
796
  * @return false|int False if the nonce is invalid, 1 if the nonce is valid
797
  * and generated between 0-12 hours ago, 2 if the nonce is
798
  * valid and generated between 12-24 hours ago.
@@ -801,24 +360,6 @@ class Admin_Init extends Init {
801
  return \check_ajax_referer( 'tsf-ajax-' . $capability, 'nonce', true );
802
  }
803
 
804
- /**
805
- * Adds removable query args to WordPress query arg handler.
806
- *
807
- * @since 2.8.0
808
- *
809
- * @param array $removable_query_args
810
- * @return array The adjusted removable query args.
811
- */
812
- public function add_removable_query_args( $removable_query_args = [] ) {
813
-
814
- if ( is_array( $removable_query_args ) ) {
815
- $removable_query_args[] = 'tsf-settings-reset';
816
- $removable_query_args[] = 'tsf-settings-updated';
817
- }
818
-
819
- return $removable_query_args;
820
- }
821
-
822
  /**
823
  * Redirect the user to an admin page, and add query args to the URL string
824
  * for alerts, etc.
@@ -846,7 +387,7 @@ class Admin_Init extends Init {
846
  }
847
 
848
  $target = \add_query_arg( $query_args, $url );
849
- $target = \esc_url_raw( $target, [ 'http', 'https' ] );
850
 
851
  //* Predict white screen:
852
  $headers_sent = headers_sent();
@@ -877,8 +418,7 @@ class Admin_Init extends Init {
877
  */
878
  protected function handle_admin_redirect_error( $target = '' ) {
879
 
880
- if ( empty( $target ) )
881
- return;
882
 
883
  $headers_list = headers_list();
884
  $location = sprintf( 'Location: %s', \wp_sanitize_redirect( $target ) );
@@ -887,17 +427,19 @@ class Admin_Init extends Init {
887
  if ( in_array( $location, $headers_list, true ) )
888
  return;
889
 
 
890
  printf( '<p><strong>%s</strong></p>',
891
  $this->convert_markdown(
892
  sprintf(
893
  /* translators: %s = Redirect URL markdown */
894
  \esc_html__( 'There has been an error redirecting. Refresh the page or follow [this link](%s).', 'autodescription' ),
895
- $target
896
  ),
897
  [ 'a' ],
898
  [ 'a_internal' => true ]
899
  )
900
  );
 
901
  }
902
 
903
  /**
@@ -909,42 +451,155 @@ class Admin_Init extends Init {
909
  */
910
  public function _wp_ajax_update_counter_type() {
911
 
912
- if ( $this->is_admin() && $this->doing_ajax() ) :
913
- $this->_check_tsf_ajax_referer( 'edit_posts' );
914
 
915
- //* Remove output buffer.
916
- $this->clean_response_header();
917
 
918
- //* If current user isn't allowed to edit posts, don't do anything and kill PHP.
919
- if ( ! \current_user_can( 'edit_posts' ) ) {
920
- //* Encode and echo results. Requires JSON decode within JS.
921
- \wp_send_json( [
922
- 'type' => 'failure',
923
- 'value' => '',
924
- ] );
925
- }
 
 
 
 
 
 
 
 
 
 
 
926
 
927
- /**
928
- * Count up, reset to 0 if needed. We have 4 options: 0, 1, 2, 3
929
- * $_POST['val'] already contains updated number.
930
- */
931
- $value = isset( $_POST['val'] ) ? intval( $_POST['val'] ) : $this->get_user_option( 0, 'counter_type', 3 ) + 1; // input var ok
932
- $value = \absint( $value );
933
 
934
- if ( $value > 3 )
935
- $value = 0;
936
 
937
- //* Update the option and get results of action.
938
- $type = $this->update_user_option( 0, 'counter_type', $value ) ? 'success' : 'error';
 
 
939
 
940
- $results = [
941
- 'type' => $type,
942
- 'value' => $value,
943
- ];
944
 
945
- //* Encode and echo results. Requires JSON decode within JS.
946
- \wp_send_json( $results );
947
- endif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
948
  }
949
 
950
  /**
@@ -952,6 +607,9 @@ class Admin_Init extends Init {
952
  *
953
  * Copied from WordPress Core wp_ajax_crop_image.
954
  * Adjusted: 1. It accepts capability 'upload_files', instead of 'customize'.
 
 
 
955
  * 2. It now only accepts TSF own AJAX nonces.
956
  * 3. It now only accepts context 'tsf-image'
957
  * 4. It no longer accepts a default context.
@@ -962,18 +620,18 @@ class Admin_Init extends Init {
962
  */
963
  public function _wp_ajax_crop_image() {
964
 
 
 
965
  $this->_check_tsf_ajax_referer( 'upload_files' );
966
- if (
967
- ! \current_user_can( 'upload_files' ) // precision alignment ok.
968
- || ! isset( $_POST['id'], $_POST['context'], $_POST['cropDetails'] ) // input var ok.
969
- ) {
970
  \wp_send_json_error();
971
  }
972
 
973
- $attachment_id = \absint( $_POST['id'] ); // input var ok.
974
 
975
- $context = str_replace( '_', '-', \sanitize_key( $_POST['context'] ) ); // input var ok.
976
- $data = array_map( 'absint', $_POST['cropDetails'] ); // input var ok.
977
  $cropped = \wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
978
 
979
  if ( ! $cropped || \is_wp_error( $cropped ) )
@@ -1000,7 +658,8 @@ class Admin_Init extends Init {
1000
  $parent_url = \wp_get_attachment_url( $attachment_id );
1001
  $url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
1002
 
1003
- $size = @getimagesize( $cropped ); // phpcs:ignore -- Feature might not be enabled.
 
1004
  $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
1005
 
1006
  $object = [
@@ -1042,5 +701,7 @@ class Admin_Init extends Init {
1042
  endswitch;
1043
 
1044
  \wp_send_json_success( \wp_prepare_attachment_for_js( $attachment_id ) );
 
 
1045
  }
1046
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Admin_Init
4
+ * @subpackage The_SEO_Framework\Admin
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
36
  class Admin_Init extends Init {
37
 
38
  /**
39
+ * Initializes SEO Bar tables.
40
  *
41
+ * @since 4.0.0
42
  * @access private
 
 
43
  */
44
+ public function _init_seo_bar_tables() {
 
 
45
 
46
+ if ( $this->get_option( 'display_seo_bar_tables' ) ) {
47
+ new Bridges\SeoBar;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
 
 
49
  }
50
 
51
  /**
52
+ * Initializes List Edit tables.
 
 
 
 
 
53
  *
54
+ * @since 4.0.0
55
+ * @access private
56
  */
57
+ public function _init_list_edit() {
58
+ new Bridges\ListEdit;
 
59
  }
60
 
61
  /**
62
+ * Adds post states in post/page edit.php query
63
  *
64
+ * @since 4.0.0
 
65
  *
66
+ * @param array $states The current post states array
67
+ * @param \WP_Post $post The Post Object.
68
+ * @return array Adjusted $states
69
  */
70
+ public function _add_post_state( $states = [], $post ) {
71
 
72
+ $post_id = isset( $post->ID ) ? $post->ID : false;
 
 
73
 
74
+ if ( $post_id ) {
75
+ $search_exclude = $this->get_option( 'alter_search_query' ) && $this->get_post_meta_item( 'exclude_local_search', $post_id );
76
+ $archive_exclude = $this->get_option( 'alter_archive_query' ) && $this->get_post_meta_item( 'exclude_from_archive', $post_id );
77
 
78
+ if ( $search_exclude )
79
+ $states[] = \esc_html__( 'No Search', 'autodescription' );
 
80
 
81
+ if ( $archive_exclude )
82
+ $states[] = \esc_html__( 'No Archive', 'autodescription' );
 
 
 
 
 
83
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ return $states;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  }
87
 
88
  /**
89
+ * Prepares scripts in the admin area.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  *
91
  * @since 3.1.0
92
+ * @since 4.0.0 Now discerns autoloading between taxonomies and singular types.
93
+ * @access private
94
  *
95
+ * @param string|null $hook The current page hook.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  */
97
+ public function _init_admin_scripts( $hook = null ) {
 
 
 
 
 
98
 
99
+ $autoenqueue = false;
 
 
 
100
 
101
+ if ( $this->is_seo_settings_page() ) {
102
+ $autoenqueue = true;
103
+ } elseif ( $hook ) {
 
 
 
104
 
105
+ $enqueue_hooks = [];
106
 
107
+ if ( $this->is_archive_admin() ) {
108
+ $prepare_edit_screen = $this->is_taxonomy_supported();
109
+ } elseif ( $this->is_singular_admin() ) {
110
+ $prepare_edit_screen = $this->is_post_type_supported();
 
 
111
  } else {
112
+ $prepare_edit_screen = false;
 
113
  }
 
 
114
 
115
+ if ( $prepare_edit_screen ) {
116
+ $enqueue_hooks = [
117
+ 'edit.php',
118
+ 'post.php',
119
+ 'post-new.php',
120
+ 'edit-tags.php',
121
+ 'term.php',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  ];
 
 
123
 
124
+ if ( ! $this->get_option( 'display_seo_bar_tables' ) ) {
125
+ $enqueue_hooks = array_diff(
126
+ $enqueue_hooks,
127
+ [
128
+ 'edit.php',
129
+ 'edit-tags.php',
130
+ ]
131
+ );
 
 
 
 
 
 
132
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  }
 
134
 
135
+ if ( in_array( $hook, $enqueue_hooks, true ) )
136
+ $autoenqueue = true;
 
 
 
137
  }
138
 
139
+ $autoenqueue and $this->init_admin_scripts();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  }
141
 
142
  /**
143
+ * Registers admin scripts and styles.
 
 
 
 
144
  *
145
+ * @since 2.6.0
146
+ * @since 3.1.0 First parameter is now deprecated.
147
+ * @since 4.0.0 First parameter is now removed.
148
  *
149
+ * @return void Early if already enqueued.
 
 
 
150
  */
151
+ public function init_admin_scripts() {
 
 
 
 
 
152
 
153
+ if ( _has_run( __METHOD__ ) ) return;
 
154
 
155
+ Bridges\Scripts::_init();
 
 
 
 
 
 
156
  }
157
 
158
  /**
159
  * Returns the title and description input guideline table, for
160
  * (Google) search, Open Graph, and Twitter.
161
  *
162
+ * NB: Some scripts have wide characters. These are recognized by Google, and have been adjusted for in the chactacter
163
+ * guidelines. German is a special Case, where we account for the Capitalization of Nouns.
164
+ *
165
+ * NB: Although the Arabic & Farsi scripts are much smaller in width, Google seems to be using the 160 & 70 char limits
166
+ * strictly... As such, we stricten the guidelines for pixels instead.
167
+ *
168
  * @since 3.1.0
169
+ * @since 4.0.0 1. Now gives different values for various WordPress locales.
170
+ * 2. Added $locale input parameter.
171
  * @staticvar array $guidelines
172
  * @TODO Consider splitting up search into Google, Bing, etc., as we might
173
  * want users to set their preferred search engine. Now, these engines
174
  * are barely any different.
175
+ * TODO move this to another object?
176
  *
177
+ * @param string|null $locale The locale to test. If empty, it will be auto-determined.
178
  * @return array
179
  */
180
+ public function get_input_guidelines( $locale = null ) {
181
+
182
+ static $guidelines = [];
183
+
184
+ $locale = $locale ?: \get_locale();
185
+
186
+ // Strip the "_formal" and other suffixes. 5 length: xx_YY
187
+ $locale = substr( $locale, 0, 5 );
188
+
189
+ if ( isset( $guidelines[ $locale ] ) )
190
+ return $guidelines[ $locale ];
191
+
192
+ // phpcs:disable, WordPress.WhiteSpace.OperatorSpacing.SpacingAfter
193
+ $character_adjustments = [
194
+ 'as' => 148 / 160, // Assamese (অসমীয়া)
195
+ 'de_AT' => 158 / 160, // Austrian German (Österreichisch Deutsch)
196
+ 'de_CH' => 158 / 160, // Swiss German (Schweiz Deutsch)
197
+ 'de_DE' => 158 / 160, // German (Deutsch)
198
+ 'gu' => 148 / 160, // Gujarati (ગુજરાતી)
199
+ 'ml_IN' => 100 / 160, // Malayalam (മലയാളം)
200
+ 'ja' => 70 / 160, // Japanese (日本語)
201
+ 'ko_KR' => 82 / 160, // Korean (한국어)
202
+ 'ta_IN' => 120 / 160, // Talim (தமிழ்)
203
+ 'zh_TW' => 70 / 160, // Taiwanese Mandarin (Traditional Chinese) (繁體中文)
204
+ 'zh_HK' => 70 / 160, // Hong Kong (Chinese version) (香港中文版)
205
+ 'zh_CN' => 70 / 160, // Mandarin (Simplified Chinese) (简体中文)
206
+ ];
207
+ // phpcs:enable, WordPress.WhiteSpace.OperatorSpacing.SpacingAfter
208
+
209
+ $c_adjust = isset( $character_adjustments[ $locale ] ) ? $character_adjustments[ $locale ] : 1;
210
+
211
+ $pixel_adjustments = [
212
+ 'ar' => 760 / 910, // Arabic (العربية)
213
+ 'ary' => 760 / 910, // Moroccan Arabic (العربية المغربية)
214
+ 'azb' => 760 / 910, // South Azerbaijani (گؤنئی آذربایجان)
215
+ 'fa_IR' => 760 / 910, // Iran Farsi (فارسی)
216
+ 'haz' => 760 / 910, // Hazaragi (هزاره گی)
217
+ 'ckb' => 760 / 910, // Central Kurdish (كوردی)
218
+ ];
219
+
220
+ $p_adjust = isset( $pixel_adjustments[ $locale ] ) ? $pixel_adjustments[ $locale ] : 1;
221
+
222
+ // phpcs:disable, WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
223
  /**
224
  * @since 3.1.0
225
  * @param array $guidelines The title and description guidelines.
226
  * Don't alter the format. Only change the numeric values.
227
  */
228
+ return $guidelines[ $locale ] = (array) \apply_filters(
229
  'the_seo_framework_input_guidelines',
230
  [
231
  'title' => [
232
  'search' => [
233
  'chars' => [
234
+ 'lower' => (int) ( 25 * $c_adjust ),
235
+ 'goodLower' => (int) ( 35 * $c_adjust ),
236
+ 'goodUpper' => (int) ( 65 * $c_adjust ),
237
+ 'upper' => (int) ( 75 * $c_adjust ),
238
  ],
239
  'pixels' => [
240
+ 'lower' => (int) ( 200 * $p_adjust ),
241
+ 'goodLower' => (int) ( 280 * $p_adjust ),
242
+ 'goodUpper' => (int) ( 520 * $p_adjust ),
243
+ 'upper' => (int) ( 600 * $p_adjust ),
244
  ],
245
  ],
246
  'opengraph' => [
265
  'description' => [
266
  'search' => [
267
  'chars' => [
268
+ 'lower' => (int) ( 45 * $c_adjust ),
269
+ 'goodLower' => (int) ( 80 * $c_adjust ),
270
+ 'goodUpper' => (int) ( 160 * $c_adjust ),
271
+ 'upper' => (int) ( 320 * $c_adjust ),
272
  ],
273
  'pixels' => [
274
+ 'lower' => (int) ( 256 * $p_adjust ),
275
+ 'goodLower' => (int) ( 455 * $p_adjust ),
276
+ 'goodUpper' => (int) ( 910 * $p_adjust ),
277
+ 'upper' => (int) ( 1820 * $p_adjust ),
278
  ],
279
  ],
280
  'opengraph' => [
298
  ],
299
  ]
300
  );
301
+ // phpcs:enable, WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
302
  }
303
 
304
  /**
307
  * Already attribute-escaped.
308
  *
309
  * @since 3.1.0
310
+ * @since 4.0.0 Now added a short leading-dot version for ARIA labels.
311
  *
312
  * @return array
313
  */
314
  public function get_input_guidelines_i18n() {
315
  return [
316
+ 'long' => [
317
  'empty' => \esc_attr__( "There's no content.", 'autodescription' ),
318
  'farTooShort' => \esc_attr__( "It's too short and it should have more information.", 'autodescription' ),
319
  'tooShort' => \esc_attr__( "It's short and it could have more information.", 'autodescription' ),
321
  'farTooLong' => \esc_attr__( "It's too long and it will get truncated in search.", 'autodescription' ),
322
  'good' => \esc_attr__( 'Length is good.', 'autodescription' ),
323
  ],
324
+ 'short' => [
325
  'empty' => \esc_attr_x( 'Empty', 'The string is empty', 'autodescription' ),
326
  'farTooShort' => \esc_attr__( 'Far too short', 'autodescription' ),
327
  'tooShort' => \esc_attr__( 'Too short', 'autodescription' ),
329
  'farTooLong' => \esc_attr__( 'Far too long', 'autodescription' ),
330
  'good' => \esc_attr__( 'Good', 'autodescription' ),
331
  ],
332
+ 'shortdot' => [
333
+ 'empty' => \esc_attr_x( 'Empty.', 'The string is empty', 'autodescription' ),
334
+ 'farTooShort' => \esc_attr__( 'Far too short.', 'autodescription' ),
335
+ 'tooShort' => \esc_attr__( 'Too short.', 'autodescription' ),
336
+ 'tooLong' => \esc_attr__( 'Too long.', 'autodescription' ),
337
+ 'farTooLong' => \esc_attr__( 'Far too long.', 'autodescription' ),
338
+ 'good' => \esc_attr__( 'Good.', 'autodescription' ),
339
+ ],
340
  ];
341
  }
342
 
351
  * @uses WP Core check_ajax_referer()
352
  * @see @link https://developer.wordpress.org/reference/functions/check_ajax_referer/
353
  *
354
+ * @param string $capability The capability that was required for the nonce check to be created.
355
  * @return false|int False if the nonce is invalid, 1 if the nonce is valid
356
  * and generated between 0-12 hours ago, 2 if the nonce is
357
  * valid and generated between 12-24 hours ago.
360
  return \check_ajax_referer( 'tsf-ajax-' . $capability, 'nonce', true );
361
  }
362
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  /**
364
  * Redirect the user to an admin page, and add query args to the URL string
365
  * for alerts, etc.
387
  }
388
 
389
  $target = \add_query_arg( $query_args, $url );
390
+ $target = \esc_url_raw( $target, [ 'https', 'http' ] );
391
 
392
  //* Predict white screen:
393
  $headers_sent = headers_sent();
418
  */
419
  protected function handle_admin_redirect_error( $target = '' ) {
420
 
421
+ if ( ! $target ) return;
 
422
 
423
  $headers_list = headers_list();
424
  $location = sprintf( 'Location: %s', \wp_sanitize_redirect( $target ) );
427
  if ( in_array( $location, $headers_list, true ) )
428
  return;
429
 
430
+ // phpcs:disable, WordPress.Security.EscapeOutput -- convert_markdown escapes. Added esc_url() for sanity.
431
  printf( '<p><strong>%s</strong></p>',
432
  $this->convert_markdown(
433
  sprintf(
434
  /* translators: %s = Redirect URL markdown */
435
  \esc_html__( 'There has been an error redirecting. Refresh the page or follow [this link](%s).', 'autodescription' ),
436
+ \esc_url( $target )
437
  ),
438
  [ 'a' ],
439
  [ 'a_internal' => true ]
440
  )
441
  );
442
+ // phpcs:enable, WordPress.Security.EscapeOutput
443
  }
444
 
445
  /**
451
  */
452
  public function _wp_ajax_update_counter_type() {
453
 
454
+ // phpcs:disable, WordPress.Security.NonceVerification -- _check_tsf_ajax_referer() does this.
455
+ $this->_check_tsf_ajax_referer( 'edit_posts' );
456
 
457
+ //* Remove output buffer.
458
+ $this->clean_response_header();
459
 
460
+ //* If current user isn't allowed to edit posts, don't do anything and kill PHP.
461
+ if ( ! \current_user_can( 'edit_posts' ) ) {
462
+ //* Encode and echo results. Requires JSON decode within JS.
463
+ \wp_send_json( [
464
+ 'type' => 'failure',
465
+ 'value' => '',
466
+ ] );
467
+ }
468
+
469
+ /**
470
+ * Count up, reset to 0 if needed. We have 4 options: 0, 1, 2, 3
471
+ * $_POST['val'] already contains updated number.
472
+ */
473
+ if ( isset( $_POST['val'] ) ) {
474
+ $value = (int) $_POST['val'];
475
+ } else {
476
+ $value = $this->get_user_option( 0, 'counter_type', 3 ) + 1;
477
+ }
478
+ $value = \absint( $value );
479
 
480
+ if ( $value > 3 )
481
+ $value = 0;
 
 
 
 
482
 
483
+ //* Update the option and get results of action.
484
+ $type = $this->update_user_option( 0, 'counter_type', $value ) ? 'success' : 'error';
485
 
486
+ $results = [
487
+ 'type' => $type,
488
+ 'value' => $value,
489
+ ];
490
 
491
+ //* Encode and echo results. Requires JSON decode within JS.
492
+ \wp_send_json( $results );
 
 
493
 
494
+ // phpcs:enable, WordPress.Security.NonceVerification
495
+ }
496
+
497
+ /**
498
+ * Gets an SEO Bar for AJAX during edit-post.
499
+ *
500
+ * @since 4.0.0
501
+ * @access private
502
+ */
503
+ public function _wp_ajax_get_post_data() {
504
+
505
+ // phpcs:disable, WordPress.Security.NonceVerification -- _check_tsf_ajax_referer() does this.
506
+ $this->_check_tsf_ajax_referer( 'edit_posts' );
507
+
508
+ // CLear output buffer.
509
+ $this->clean_response_header();
510
+
511
+ $post_id = \absint( $_POST['post_id'] );
512
+
513
+ if ( ! $post_id || ! \current_user_can( 'edit_post', $post_id ) ) {
514
+ \wp_send_json( [
515
+ 'type' => 'failure',
516
+ 'data' => [],
517
+ ] );
518
+ }
519
+
520
+ $_get_defaults = [
521
+ 'seobar' => false,
522
+ 'metadescription' => false,
523
+ 'ogdescription' => false,
524
+ 'twdescription' => false,
525
+ 'imageurl' => false,
526
+ ];
527
+
528
+ // Only get what's indexed in the defaults and set as "true".
529
+ $get = array_keys(
530
+ array_filter(
531
+ array_intersect_key(
532
+ array_merge(
533
+ $_get_defaults,
534
+ (array) ( isset( $_POST['get'] ) ? $_POST['get'] : [] )
535
+ ),
536
+ $_get_defaults
537
+ )
538
+ )
539
+ );
540
+
541
+ $_generator_args = [
542
+ 'id' => $post_id,
543
+ 'taxonomy' => '',
544
+ ];
545
+
546
+ $data = [];
547
+
548
+ foreach ( $get as $g ) :
549
+ switch ( $g ) {
550
+ case 'seobar':
551
+ $data[ $g ] = $this->get_generated_seo_bar( $_generator_args );
552
+ break;
553
+
554
+ case 'metadescription':
555
+ case 'ogdescription':
556
+ case 'twdescription':
557
+ switch ( $g ) {
558
+ case 'metadescription':
559
+ if ( $this->is_static_frontpage( $post_id ) ) {
560
+ // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
561
+ $data[ $g ] = $this->get_option( 'homepage_description' )
562
+ ?: $this->get_generated_description( $_generator_args, false );
563
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
564
+ } else {
565
+ $data[ $g ] = $this->get_generated_description( $_generator_args, false );
566
+ }
567
+ break;
568
+ case 'ogdescription':
569
+ // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- Smart loop.
570
+ $_social_ph = isset( $_social_ph ) ? $_social_ph : $this->_get_social_placeholders( $_generator_args );
571
+ $data[ $g ] = $_social_ph['description']['og'];
572
+ break;
573
+ case 'twdescription':
574
+ // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- Smart loop.
575
+ $_social_ph = isset( $_social_ph ) ? $_social_ph : $this->_get_social_placeholders( $_generator_args );
576
+ $data[ $g ] = $_social_ph['description']['twitter'];
577
+ break;
578
+ }
579
+
580
+ $data[ $g ] = Bridges\Scripts::decode_entities( $this->s_description( $data[ $g ] ) );
581
+ break;
582
+
583
+ case 'imageurl':
584
+ if ( $this->is_static_frontpage( $post_id ) && $this->get_option( 'homepage_social_image_url' ) ) {
585
+ $image_details = current( $this->get_image_details( $_generator_args, true, 'social', true ) );
586
+ $data[ $g ] = isset( $image_details['url'] ) ? $image_details['url'] : '';
587
+ } else {
588
+ $image_details = current( $this->get_generated_image_details( $_generator_args, true, 'social', true ) );
589
+ $data[ $g ] = isset( $image_details['url'] ) ? $image_details['url'] : '';
590
+ }
591
+ break;
592
+
593
+ default:
594
+ break;
595
+ }
596
+ endforeach;
597
+
598
+ \wp_send_json( [
599
+ 'type' => 'success',
600
+ 'data' => $data,
601
+ 'processed' => $get,
602
+ ] );
603
  }
604
 
605
  /**
607
  *
608
  * Copied from WordPress Core wp_ajax_crop_image.
609
  * Adjusted: 1. It accepts capability 'upload_files', instead of 'customize'.
610
+ * - This was set to 'edit_post' in WP 4.7? trac ticket got lost, probably for (invalid) security reasons.
611
+ * In any case, that's still incorrect, and I gave up on communicating this;
612
+ * We're not editing the image, we're creating a new one!
613
  * 2. It now only accepts TSF own AJAX nonces.
614
  * 3. It now only accepts context 'tsf-image'
615
  * 4. It no longer accepts a default context.
620
  */
621
  public function _wp_ajax_crop_image() {
622
 
623
+ // This checks the nonce, re:to all 'WordPress.Security.NonceVerification' below
624
+ // phpcs:disable, WordPress.Security.NonceVerification -- _check_tsf_ajax_referer does this.
625
  $this->_check_tsf_ajax_referer( 'upload_files' );
626
+
627
+ if ( ! \current_user_can( 'upload_files' ) || ! isset( $_POST['id'], $_POST['context'], $_POST['cropDetails'] ) ) {
 
 
628
  \wp_send_json_error();
629
  }
630
 
631
+ $attachment_id = \absint( $_POST['id'] );
632
 
633
+ $context = str_replace( '_', '-', \sanitize_key( $_POST['context'] ) );
634
+ $data = array_map( 'absint', $_POST['cropDetails'] );
635
  $cropped = \wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
636
 
637
  if ( ! $cropped || \is_wp_error( $cropped ) )
658
  $parent_url = \wp_get_attachment_url( $attachment_id );
659
  $url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
660
 
661
+ // phpcs:ignore, WordPress.PHP.NoSilencedErrors -- Feature may be disabled.
662
+ $size = @getimagesize( $cropped );
663
  $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
664
 
665
  $object = [
701
  endswitch;
702
 
703
  \wp_send_json_success( \wp_prepare_attachment_for_js( $attachment_id ) );
704
+
705
+ // phpcs:enable, WordPress.Security.NonceVerification
706
  }
707
  }
inc/classes/admin-pages.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -30,15 +32,7 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
30
  *
31
  * @since 2.8.0
32
  */
33
- class Admin_Pages extends Inpost {
34
-
35
- /**
36
- * @since 2.2.2
37
- * @access private
38
- * We're going to remove this.
39
- * @var array $page_defaults Holds Page output defaults.
40
- */
41
- public $page_defaults = [];
42
 
43
  /**
44
  * @since 2.7.0
@@ -52,51 +46,24 @@ class Admin_Pages extends Inpost {
52
  */
53
  public $load_options;
54
 
55
- /**
56
- * Enqueues page defaults early.
57
- *
58
- * @since 2.3.1
59
- */
60
- public function enqueue_page_defaults() {
61
- /**
62
- * @since 2.3.1
63
- * @param array $page_defaults The admin default notice sentences.
64
- */
65
- $this->page_defaults = (array) \apply_filters(
66
- 'the_seo_framework_admin_page_defaults',
67
- [
68
- 'save_button_text' => \esc_html__( 'Save Settings', 'autodescription' ),
69
- 'reset_button_text' => \esc_html__( 'Reset Settings', 'autodescription' ),
70
- 'saved_notice_text' => \esc_html__( 'Settings are saved.', 'autodescription' ),
71
- 'reset_notice_text' => \esc_html__( 'Settings are reset.', 'autodescription' ),
72
- 'error_notice_text' => \esc_html__( 'Error saving settings.', 'autodescription' ),
73
- 'plugin_update_text' => \esc_html__( 'New SEO Settings have been updated.', 'autodescription' ),
74
- ]
75
- );
76
- }
77
-
78
  /**
79
  * Adds menu links under "settings" in the wp-admin dashboard
80
  *
81
  * @since 2.2.2
82
  * @since 2.9.2 Added static cache so the method can only run once.
83
- * @staticvar bool $run True if already run.
84
  *
85
  * @return void Early if method is already called.
86
  */
87
  public function add_menu_link() {
88
 
89
- static $run = false;
90
-
91
- if ( $run )
92
- return;
93
 
94
  $menu = [
95
  'page_title' => \esc_html__( 'SEO Settings', 'autodescription' ),
96
  'menu_title' => \esc_html__( 'SEO', 'autodescription' ),
97
  'capability' => $this->get_settings_capability(),
98
  'menu_slug' => $this->seo_settings_page_slug,
99
- 'callback' => [ $this, '_output_seo_settings_wrap' ],
100
  'icon' => 'dashicons-search',
101
  'position' => '90.9001',
102
  ];
@@ -126,200 +93,132 @@ class Admin_Pages extends Inpost {
126
 
127
  //* Enqueue scripts
128
  \add_action( 'admin_print_scripts-' . $this->seo_settings_page_hook, [ $this, '_init_admin_scripts' ], 11 );
 
 
129
 
130
- $run = true;
 
 
 
 
 
 
 
 
131
  }
132
 
133
  /**
134
- * Initialize the settings page.
135
  *
136
- * @since 2.2.2
137
- * @since 2.8.0 Handled settings POST initialization.
138
  */
139
- public function settings_init() {
140
 
141
- //* Handle post-update actions. Must be initialized on admin_init and is initalized on options.php.
142
- if ( 'options.php' === $GLOBALS['pagenow'] )
143
- $this->handle_update_post();
 
144
 
145
- //* Output metaboxes.
146
- \add_action( $this->seo_settings_page_hook . '_settings_page_boxes', [ $this, '_output_seo_settings_columns' ] );
147
- \add_action( 'load-' . $this->seo_settings_page_hook, [ $this, '_register_seo_settings_metaboxes' ] );
148
  }
149
 
150
  /**
151
- * Outputs SEO Settings page wrap.
152
  *
153
- * @since 3.0.0
154
- * @access private
 
 
155
  */
156
- public function _output_seo_settings_wrap() {
157
- /**
158
- * @since 3.0.0
159
- */
160
- \do_action( 'the_seo_framework_pre_seo_settings' );
161
- $this->get_view( 'admin/seo-settings-wrap', get_defined_vars() );
162
  /**
163
- * @since 3.0.0
 
164
  */
165
- \do_action( 'the_seo_framework_pro_seo_settings' );
 
 
 
 
 
 
166
  }
167
 
168
  /**
169
- * Outputs SEO Settings columns.
170
  *
171
- * @since 3.0.0
172
- * @access private
173
  */
174
- public function _output_seo_settings_columns() {
175
- $this->get_view( 'admin/seo-settings-columns', get_defined_vars() );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  }
177
 
178
  /**
179
- * Registers meta boxes on the Site SEO Settings page.
180
  *
181
- * @since 3.0.0
182
  * @access private
183
- * @see $this->general_metabox() Callback for General Settings box.
184
- * @see $this->title_metabox() Callback for Title Settings box.
185
- * @see $this->description_metabox() Callback for Description Settings box.
186
- * @see $this->robots_metabox() Callback for Robots Settings box.
187
- * @see $this->homepage_metabox() Callback for Homepage Settings box.
188
- * @see $this->social_metabox() Callback for Social Settings box.
189
- * @see $this->schema_metabox() Callback for Schema Settings box.
190
- * @see $this->webmaster_metabox() Callback for Webmaster Settings box.
191
- * @see $this->sitemaps_metabox() Callback for Sitemap Settings box.
192
- * @see $this->feed_metabox() Callback for Feed Settings box.
193
  */
194
- public function _register_seo_settings_metaboxes() {
195
 
196
- /**
197
- * Various metabox filters.
198
- * Set any to false if you wish the meta box to be removed.
199
- *
200
- * @since 2.2.4
201
- * @since 2.8.0: Added `the_seo_framework_general_metabox` filter.
202
- */
203
- $general = (bool) \apply_filters( 'the_seo_framework_general_metabox', true );
204
- $title = (bool) \apply_filters( 'the_seo_framework_title_metabox', true );
205
- $description = (bool) \apply_filters( 'the_seo_framework_description_metabox', true );
206
- $robots = (bool) \apply_filters( 'the_seo_framework_robots_metabox', true );
207
- $home = (bool) \apply_filters( 'the_seo_framework_home_metabox', true );
208
- $social = (bool) \apply_filters( 'the_seo_framework_social_metabox', true );
209
- $schema = (bool) \apply_filters( 'the_seo_framework_schema_metabox', true );
210
- $webmaster = (bool) \apply_filters( 'the_seo_framework_webmaster_metabox', true );
211
- $sitemap = (bool) \apply_filters( 'the_seo_framework_sitemap_metabox', true );
212
- $feed = (bool) \apply_filters( 'the_seo_framework_feed_metabox', true );
213
-
214
- //* Title Meta Box
215
- if ( $general )
216
- \add_meta_box(
217
- 'autodescription-general-settings',
218
- \esc_html__( 'General Settings', 'autodescription' ),
219
- [ $this, 'general_metabox' ],
220
- $this->seo_settings_page_hook,
221
- 'main',
222
- []
223
- );
224
 
225
- //* Title Meta Box
226
- if ( $title )
227
- \add_meta_box(
228
- 'autodescription-title-settings',
229
- \esc_html__( 'Title Settings', 'autodescription' ),
230
- [ $this, 'title_metabox' ],
231
- $this->seo_settings_page_hook,
232
- 'main',
233
- []
234
- );
235
 
236
- //* Description Meta Box
237
- if ( $description )
238
- \add_meta_box(
239
- 'autodescription-description-settings',
240
- \esc_html__( 'Description Meta Settings', 'autodescription' ),
241
- [ $this, 'description_metabox' ],
242
- $this->seo_settings_page_hook,
243
- 'main',
244
- []
245
- );
246
 
247
- //* Homepage Meta Box
248
- if ( $home )
249
- \add_meta_box(
250
- 'autodescription-homepage-settings',
251
- \esc_html__( 'Homepage Settings', 'autodescription' ),
252
- [ $this, 'homepage_metabox' ],
253
- $this->seo_settings_page_hook,
254
- 'main',
255
- []
256
- );
257
 
258
- //* Social Meta Box
259
- if ( $social )
260
- \add_meta_box(
261
- 'autodescription-social-settings',
262
- \esc_html__( 'Social Meta Settings', 'autodescription' ),
263
- [ $this, 'social_metabox' ],
264
- $this->seo_settings_page_hook,
265
- 'main',
266
- []
267
- );
268
 
269
- //* Title Meta Box
270
- if ( $schema )
271
- \add_meta_box(
272
- 'autodescription-schema-settings',
273
- \esc_html__( 'Schema.org Settings', 'autodescription' ),
274
- [ $this, 'schema_metabox' ],
275
- $this->seo_settings_page_hook,
276
- 'main',
277
- []
278
- );
279
 
280
- //* Robots Meta Box
281
- if ( $robots )
282
- \add_meta_box(
283
- 'autodescription-robots-settings',
284
- \esc_html__( 'Robots Meta Settings', 'autodescription' ),
285
- [ $this, 'robots_metabox' ],
286
- $this->seo_settings_page_hook,
287
- 'main',
288
- []
289
- );
290
 
291
- //* Webmaster Meta Box
292
- if ( $webmaster )
293
- \add_meta_box(
294
- 'autodescription-webmaster-settings',
295
- \esc_html__( 'Webmaster Meta Settings', 'autodescription' ),
296
- [ $this, 'webmaster_metabox' ],
297
- $this->seo_settings_page_hook,
298
- 'main',
299
- []
300
- );
301
 
302
- //* Sitemaps Meta Box
303
- if ( $sitemap )
304
- \add_meta_box(
305
- 'autodescription-sitemap-settings',
306
- \esc_html__( 'Sitemap Settings', 'autodescription' ),
307
- [ $this, 'sitemaps_metabox' ],
308
- $this->seo_settings_page_hook,
309
- 'main',
310
- []
311
- );
312
 
313
- //* Feed Meta Box
314
- if ( $feed )
315
- \add_meta_box(
316
- 'autodescription-feed-settings',
317
- \esc_html__( 'Feed Settings', 'autodescription' ),
318
- [ $this, 'feed_metabox' ],
319
- $this->seo_settings_page_hook,
320
- 'main',
321
- []
322
- );
323
  }
324
 
325
  /**
@@ -340,36 +239,54 @@ class Admin_Pages extends Inpost {
340
  );
341
  $this->update_static_cache( 'check_seo_plugin_conflicts', 0 );
342
  }
 
343
 
344
- if ( $this->is_seo_settings_page( true ) ) {
345
- $this->do_settings_page_notices();
346
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  }
348
 
349
  /**
350
- * Display notices on SEO setting changes.
351
- *
352
- * @since 3.1.0
353
- * @securitycheck 3.0.0 OK. NOTE: Users can however MANUALLY trigger these on the SEO settings page.
354
- * @todo convert the "get" into secure "error_notice" option. See TSF Extension Manager.
355
- * @todo convert $this->page_defaults to inline texts. It's now uselessly rendering.
 
 
 
 
 
 
 
 
 
 
 
356
  */
357
- protected function do_settings_page_notices() {
358
-
359
- $get = empty( $_GET ) ? null : $_GET; // CSRF, input var OK.
360
-
361
- if ( null === $get )
362
- return;
363
-
364
- if ( isset( $get['settings-updated'] ) && 'true' === $get['settings-updated'] ) :
365
- $this->do_dismissible_notice( $this->page_defaults['saved_notice_text'], 'updated' );
366
- elseif ( isset( $get['tsf-settings-reset'] ) && 'true' === $get['tsf-settings-reset'] ) :
367
- $this->do_dismissible_notice( $this->page_defaults['reset_notice_text'], 'warning' );
368
- elseif ( isset( $get['error'] ) && 'true' === $get['error'] ) :
369
- $this->do_dismissible_notice( $this->page_defaults['error_notice_text'], 'error' );
370
- elseif ( isset( $get['tsf-settings-updated'] ) && 'true' === $get['tsf-settings-updated'] ) :
371
- $this->do_dismissible_notice( $this->page_defaults['plugin_update_text'], 'updated' );
372
- endif;
373
  }
374
 
375
  /**
@@ -384,7 +301,7 @@ class Admin_Pages extends Inpost {
384
  * @return string Full field name
385
  */
386
  public function get_field_name( $name ) {
387
- return sprintf( '%s[%s]', $this->settings_field, $name );
388
  }
389
 
390
  /**
@@ -408,7 +325,7 @@ class Admin_Pages extends Inpost {
408
  * @return string Full field id
409
  */
410
  public function get_field_id( $id ) {
411
- return sprintf( '%s[%s]', $this->settings_field, $id );
412
  }
413
 
414
  /**
@@ -417,7 +334,7 @@ class Admin_Pages extends Inpost {
417
  * @since 2.2.2
418
  * @uses $this->get_field_id() Constructs id attributes for use in form fields.
419
  *
420
- * @param string $id Field id base.
421
  * @param boolean $echo Whether to escape echo or just return.
422
  * @return string Full field id
423
  */
@@ -435,11 +352,12 @@ class Admin_Pages extends Inpost {
435
  *
436
  * @since 2.6.0
437
  * @since 3.0.6 The messages are no longer auto-styled to "strong".
 
438
  *
439
  * @param string $message The notice message. Expected to be escaped if $escape is false.
440
  * @param string $type The notice type : 'updated', 'error', 'warning'. Expected to be escaped.
441
- * @param bool $a11y Whether to add an accessibility icon.
442
- * @param bool $escape Whether to escape the whole output.
443
  * @return string The dismissible error notice.
444
  */
445
  public function generate_dismissible_notice( $message = '', $type = 'updated', $a11y = true, $escape = true ) {
@@ -449,9 +367,7 @@ class Admin_Pages extends Inpost {
449
  //* Make sure the scripts are loaded.
450
  $this->init_admin_scripts();
451
 
452
- //! PHP 5.4 compat: put in var.
453
- $scripts = $this->Scripts();
454
- $scripts::enqueue();
455
 
456
  if ( 'warning' === $type )
457
  $type = 'notice-warning';
@@ -459,16 +375,15 @@ class Admin_Pages extends Inpost {
459
  $a11y = $a11y ? 'tsf-show-icon' : '';
460
 
461
  return vsprintf(
462
- '<div class="notice %s tsf-notice %s"><p>%s%s</p></div>',
463
  [
464
  \esc_attr( $type ),
465
  ( $a11y ? 'tsf-show-icon' : '' ),
 
466
  sprintf(
467
- '<a class="hide-if-no-js tsf-dismiss" title="%s" %s></a>',
468
- \esc_attr__( 'Dismiss', 'autodescription' ),
469
- ''
470
  ),
471
- ( $escape ? \esc_html( $message ) : $message ),
472
  ]
473
  );
474
  }
@@ -478,13 +393,14 @@ class Admin_Pages extends Inpost {
478
  *
479
  * @since 2.7.0
480
  *
481
- * @param $message The notice message. Expected to be escaped if $escape is false.
482
- * @param $type The notice type : 'updated', 'error', 'warning'. Expected to be escaped.
483
- * @param bool $a11y Whether to add an accessibility icon.
484
- * @param bool $escape Whether to escape the whole output.
485
  */
486
  public function do_dismissible_notice( $message = '', $type = 'updated', $a11y = true, $escape = true ) {
487
- echo $this->generate_dismissible_notice( $message, $type, (bool) $a11y, (bool) $escape ); // xss ok
 
488
  }
489
 
490
  /**
@@ -495,11 +411,12 @@ class Admin_Pages extends Inpost {
495
  * @see $this->do_dismissible_sticky_notice()
496
  * @uses THE_SEO_FRAMEWORK_UPDATES_CACHE
497
  * @todo make this do something.
 
498
  * NOTE: This method is a placeholder.
499
  *
500
  * @param string $message The notice message. Expected to be escaped if $escape is false.
501
  * @param string $key The notice key. Must be unique and tied to the stored updates cache option.
502
- * @param array $args : {
503
  * 'type' => string Optional. The notification type. Default 'updated'.
504
  * 'a11y' => bool Optional. Whether to enable accessibility. Default true.
505
  * 'escape' => bool Optional. Whether to escape the $message. Default true.
@@ -508,7 +425,7 @@ class Admin_Pages extends Inpost {
508
  * }
509
  * @return string The dismissible error notice.
510
  */
511
- public function generate_dismissible_sticky_notice( $message, $key, $args = [] ) {
512
  return '';
513
  }
514
 
@@ -517,20 +434,21 @@ class Admin_Pages extends Inpost {
517
  *
518
  * @since 2.9.3
519
  * @uses $this->generate_dismissible_sticky_notice()
 
520
  *
521
  * @param string $message The notice message. Expected to be escaped if $escape is false.
522
  * @param string $key The notice key. Must be unique and tied to the stored updates cache option.
523
- * @param array $args : {
524
  * 'type' => string Optional. The notification type. Default 'updated'.
525
  * 'a11y' => bool Optional. Whether to enable accessibility. Default true.
526
  * 'escape' => bool Optional. Whether to escape the $message. Default true.
527
  * 'color' => string Optional. If filled in, it will output the selected color. Default ''.
528
  * 'icon' => string Optional. If filled in, it will output the selected icon. Default ''.
529
  * }
530
- * @return string The dismissible error notice.
531
  */
532
  public function do_dismissible_sticky_notice( $message, $key, $args = [] ) {
533
- echo $this->generate_dismissible_sticky_notice( $message, $key, $args ); // xss ok
 
534
  }
535
 
536
  /**
@@ -567,7 +485,6 @@ class Admin_Pages extends Inpost {
567
  *
568
  * @param string $content Content to be wrapped in the description wrap.
569
  * @param bool $block Whether to wrap the content in <p> tags.
570
- * @return string Content wrapped in the description wrap.
571
  */
572
  public function description( $content, $block = true ) {
573
  $this->description_noesc( \esc_html( $content ), $block );
@@ -580,11 +497,11 @@ class Admin_Pages extends Inpost {
580
  *
581
  * @param string $content Content to be wrapped in the description wrap. Expected to be escaped.
582
  * @param bool $block Whether to wrap the content in <p> tags.
583
- * @return string Content wrapped in the description wrap.
584
  */
585
  public function description_noesc( $content, $block = true ) {
586
  $output = '<span class="description">' . $content . '</span>';
587
- echo $block ? '<p>' . $output . '</p>' : $output; // xss: method name explains
 
588
  }
589
 
590
  /**
@@ -595,7 +512,6 @@ class Admin_Pages extends Inpost {
595
  *
596
  * @param string $content Content to be wrapped in the attention wrap.
597
  * @param bool $block Whether to wrap the content in <p> tags.
598
- * @return string Content wrapped in the attention wrap.
599
  */
600
  public function attention( $content, $block = true ) {
601
  $this->attention_noesc( \esc_html( $content ), $block );
@@ -608,11 +524,11 @@ class Admin_Pages extends Inpost {
608
  *
609
  * @param string $content Content to be wrapped in the attention wrap. Expected to be escaped.
610
  * @param bool $block Whether to wrap the content in <p> tags.
611
- * @return string Content wrapped in the attention wrap.
612
  */
613
  public function attention_noesc( $content, $block = true ) {
614
  $output = '<span class="attention">' . $content . '</span>';
615
- echo $block ? '<p>' . $output . '</p>' : $output; // xss: method name explains
 
616
  }
617
 
618
  /**
@@ -623,7 +539,6 @@ class Admin_Pages extends Inpost {
623
  *
624
  * @param string $content Content to be wrapped in the wrap. Expected to be escaped.
625
  * @param bool $block Whether to wrap the content in <p> tags.
626
- * @return string Content wrapped in the wrap.
627
  */
628
  public function attention_description( $content, $block = true ) {
629
  $this->attention_description_noesc( \esc_html( $content ), $block );
@@ -636,38 +551,22 @@ class Admin_Pages extends Inpost {
636
  *
637
  * @param string $content Content to be wrapped in the wrap. Expected to be escaped.
638
  * @param bool $block Whether to wrap the content in <p> tags.
639
- * @return string Content wrapped in the wrap.
640
  */
641
  public function attention_description_noesc( $content, $block = true ) {
642
  $output = '<span class="description attention">' . $content . '</span>';
643
- echo $block ? '<p>' . $output . '</p>' : $output; // xss: method name explains
644
- }
645
-
646
- /**
647
- * Google docs language determinator.
648
- *
649
- * @since 2.2.2
650
- * @staticvar string $language
651
- *
652
- * @return string language code
653
- */
654
- protected function google_language() {
655
-
656
- static $language = null;
657
-
658
- if ( isset( $language ) ) return $language;
659
-
660
- /* translators: Language shorttag to be used in Google help pages. */
661
- return $language = \esc_html_x( 'en', 'e.g. en for English, nl for Dutch, fi for Finish, de for German', 'autodescription' );
662
  }
663
 
664
  /**
665
  * Echo or return a chechbox fields wrapper.
666
  *
 
 
667
  * @since 2.6.0
668
  *
669
  * @param string $input The input to wrap. Should already be escaped.
670
- * @param boolean $echo Whether to escape echo or just return.
671
  * @return string|void Wrapped $input.
672
  */
673
  public function wrap_fields( $input = '', $echo = false ) {
@@ -676,12 +575,55 @@ class Admin_Pages extends Inpost {
676
  $input = implode( PHP_EOL, $input );
677
 
678
  if ( $echo ) {
679
- echo '<div class="tsf-fields">' . $input . '</div>'; // xss user warning.
 
680
  } else {
681
  return '<div class="tsf-fields">' . $input . '</div>';
682
  }
683
  }
684
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
685
  /**
686
  * Returns a chechbox wrapper.
687
  *
@@ -714,36 +656,39 @@ class Admin_Pages extends Inpost {
714
  * @since 3.1.0
715
  *
716
  * @param array $args : {
717
- * @type string $id The option name, used as field ID.
718
- * @type string $index The option index, used when the option is an array.
719
- * @type string $label The checkbox label description, placed inline of the checkbox.
720
- * @type string $description The checkbox additional description, placed underneat.
721
- * @type bool $escape Whether to enable escaping of the $label and $description.
722
- * @type bool $disabled Whether to disable the checkbox field.
723
- * @type bool $default Whether to display-as-default. This is autodetermined when no $index is set.
724
- * @type bool $warned Whether to warn the checkbox field value.
725
  * }
726
  * @return string HTML checkbox output.
727
  */
728
  public function make_checkbox_array( array $args = [] ) {
729
 
730
- $args = array_merge( [
731
- 'id' => '',
732
- 'index' => '',
733
- 'label' => '',
734
- 'description' => '',
735
- 'escape' => true,
736
- 'disabled' => false,
737
- 'default' => false,
738
- 'warned' => false,
739
- ], $args );
 
 
 
740
 
741
  if ( $args['escape'] ) {
742
  $args['description'] = \esc_html( $args['description'] );
743
  $args['label'] = \esc_html( $args['label'] );
744
  }
745
 
746
- $index = $this->sanitize_field_id( $args['index'] ?: '' );
747
 
748
  $field_id = $field_name = \esc_attr( sprintf(
749
  '%s%s',
@@ -797,15 +742,102 @@ class Admin_Pages extends Inpost {
797
  return $output;
798
  }
799
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
800
  /**
801
  * Return a wrapped question mark.
802
  *
803
  * @since 2.6.0
804
  * @since 3.0.0 Links are now no longer followed, referred or bound to opener.
 
805
  *
806
  * @param string $description The descriptive on-hover title.
807
- * @param string $link The non-escaped link.
808
- * @param bool $echo Whether to echo or return.
809
  * @return string HTML checkbox output if $echo is false.
810
  */
811
  public function make_info( $description = '', $link = '', $echo = true ) {
@@ -813,12 +845,12 @@ class Admin_Pages extends Inpost {
813
  if ( $link ) {
814
  $output = sprintf(
815
  '<a href="%1$s" class="tsf-tooltip-item tsf-help" target="_blank" rel="nofollow noreferrer noopener" title="%2$s" data-desc="%2$s">[?]</a>',
816
- \esc_url( $link, [ 'http', 'https' ] ),
817
  \esc_attr( $description )
818
  );
819
  } else {
820
  $output = sprintf(
821
- '<span class="tsf-tooltip-item tsf-help" title="%1$s" data-desc="%1$s">[?]</span>',
822
  \esc_attr( $description )
823
  );
824
  }
@@ -826,33 +858,13 @@ class Admin_Pages extends Inpost {
826
  $output = sprintf( '<span class="tsf-tooltip-wrap">%s</span>', $output );
827
 
828
  if ( $echo ) {
829
- echo $output; // xss ok
 
830
  } else {
831
  return $output;
832
  }
833
  }
834
 
835
- /**
836
- * Load script and stylesheet assets via metabox_scripts() methods.
837
- *
838
- * @since 2.2.2
839
- */
840
- public function load_assets() {
841
- //* Hook scripts method
842
- \add_action( "load-{$this->seo_settings_page_hook}", [ $this, 'metabox_scripts' ] );
843
- }
844
-
845
- /**
846
- * Includes the necessary sortable metabox scripts.
847
- *
848
- * @since 2.2.2
849
- */
850
- public function metabox_scripts() {
851
- \wp_enqueue_script( 'common' );
852
- \wp_enqueue_script( 'wp-lists' );
853
- \wp_enqueue_script( 'postbox' );
854
- }
855
-
856
  /**
857
  * Returns the HTML class wrap for default Checkbox options.
858
  *
@@ -945,10 +957,10 @@ class Admin_Pages extends Inpost {
945
  * @since 2.3.4
946
  * @since 3.1.0 Deprecated second parameter.
947
  *
948
- * @param string $key The option name which returns boolean.
949
- * @param string $setting optional The settings field.
950
- * @param bool $wrap Whether to wrap the class name in `class="%s"`
951
- * @param bool $echo Whether to echo or return the output.
952
  * @return string Empty on echo or the class name with an optional wrapper.
953
  */
954
  public function is_conditional_checked( $key, $deprecated = '', $wrap = true, $echo = true ) {
@@ -980,12 +992,31 @@ class Admin_Pages extends Inpost {
980
  }
981
  }
982
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
983
  /**
984
  * Returns social image uploader form button.
985
  * Also registers additional i18n strings for JS.
986
  *
987
  * @since 2.8.0
988
  * @since 3.1.0 No longer prepares media l10n data.
 
989
  *
990
  * @param string $input_id Required. The HTML input id to pass URL into.
991
  * @return string The image uploader button.
@@ -998,19 +1029,30 @@ class Admin_Pages extends Inpost {
998
  $s_input_id = \esc_attr( $input_id );
999
 
1000
  $content = vsprintf(
1001
- '<button type=button data-href="%1$s" class="tsf-set-image-button button button-primary button-small" title="%2$s" id="%3$s-select"
1002
- data-input-id="%3$s" data-input-type="social" data-width="%4$s" data-height="%5$s" data-flex="%6$d">%7$s</button>',
1003
  [
1004
  \esc_url( \get_upload_iframe_src( 'image', $this->get_the_real_ID() ) ),
1005
  \esc_attr_x( 'Select social image', 'Button hover', 'autodescription' ),
1006
  $s_input_id,
1007
- '1200',
1008
- '630',
1009
- true,
 
 
 
 
 
 
1010
  \esc_html__( 'Select Image', 'autodescription' ),
1011
  ]
1012
  );
1013
 
 
 
 
 
 
1014
  return $content;
1015
  }
1016
 
@@ -1020,6 +1062,7 @@ class Admin_Pages extends Inpost {
1020
  *
1021
  * @since 3.0.0
1022
  * @since 3.1.0 No longer prepares media l10n data.
 
1023
  *
1024
  * @param string $input_id Required. The HTML input id to pass URL into.
1025
  * @return string The image uploader button.
@@ -1032,19 +1075,30 @@ class Admin_Pages extends Inpost {
1032
  $s_input_id = \esc_attr( $input_id );
1033
 
1034
  $content = vsprintf(
1035
- '<button type=button data-href="%1$s" class="tsf-set-image-button button button-primary button-small" title="%2$s" id="%3$s-select"
1036
- data-input-id="%3$s" data-input-type="logo" data-width="%4$s" data-height="%5$s" data-flex="%6$d">%7$s</button>',
1037
  [
1038
  \esc_url( \get_upload_iframe_src( 'image', $this->get_the_real_ID() ) ),
1039
- '',
1040
  $s_input_id,
1041
- '512',
1042
- '512',
1043
- false,
 
 
 
 
 
 
1044
  \esc_html__( 'Select Logo', 'autodescription' ),
1045
  ]
1046
  );
1047
 
 
 
 
 
 
1048
  return $content;
1049
  }
1050
 
@@ -1054,10 +1108,10 @@ class Admin_Pages extends Inpost {
1054
  * @since 3.0.4
1055
  */
1056
  public function output_js_title_elements() {
1057
- echo '<span id="tsf-title-reference" style="display:none"></span>';
1058
- echo '<span id="tsf-title-offset" class="hide-if-no-js"></span>';
1059
- echo '<span id="tsf-title-placeholder" class="hide-if-no-js"></span>';
1060
- echo '<span id="tsf-title-placeholder-prefix" class="hide-if-no-js"></span>';
1061
  }
1062
 
1063
  /**
@@ -1078,15 +1132,15 @@ class Admin_Pages extends Inpost {
1078
  * 3. The whole output is now hidden from no-js.
1079
  *
1080
  * @param string $for The input ID it's for.
1081
- * @param string $initial The initial value for no-JS. Deprecated.
1082
  * @param bool $display Whether to display the counter. (options page gimmick)
1083
  */
1084
- public function output_character_counter_wrap( $for, $initial = '', $display = true ) {
1085
  vprintf(
1086
- '<div class="tsf-counter-wrap hide-if-no-js" %s><span class="description tsf-counter" title="%s">%s</span><span class="tsf-ajax"></span></div>',
1087
  [
1088
  ( $display ? '' : 'style="display:none;"' ),
1089
- \esc_attr( 'Click to change the counter type', 'autodescription' ),
1090
  sprintf(
1091
  /* translators: %s = number */
1092
  \esc_html__( 'Characters Used: %s', 'autodescription' ),
@@ -1111,13 +1165,13 @@ class Admin_Pages extends Inpost {
1111
  */
1112
  public function output_pixel_counter_wrap( $for, $type, $display = true ) {
1113
  vprintf(
1114
- '<div class="tsf-pixel-counter-wrap hide-if-no-js" %s>%s%s</div>',
1115
  [
1116
  ( $display ? '' : 'style="display:none;"' ),
1117
  sprintf(
1118
  '<div id="%s_pixels" class="tsf-tooltip-wrap">%s</div>',
1119
  \esc_attr( $for ),
1120
- '<span class="tsf-pixel-counter-bar tsf-tooltip-item" aria-label="" data-desc=""><span class="tsf-pixel-counter-fluid"></span></span>'
1121
  ),
1122
  sprintf(
1123
  '<div class="tsf-pixel-shadow-wrap"><span class="tsf-pixel-counter-shadow tsf-%s-pixel-counter-shadow"></span></div>',
@@ -1126,4 +1180,131 @@ class Admin_Pages extends Inpost {
1126
  ]
1127
  );
1128
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1129
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Admin_Pages
4
+ * @subpackage The_SEO_Framework\Admin\Settings
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
32
  *
33
  * @since 2.8.0
34
  */
35
+ class Admin_Pages extends Profile {
 
 
 
 
 
 
 
 
36
 
37
  /**
38
  * @since 2.7.0
46
  */
47
  public $load_options;
48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  /**
50
  * Adds menu links under "settings" in the wp-admin dashboard
51
  *
52
  * @since 2.2.2
53
  * @since 2.9.2 Added static cache so the method can only run once.
 
54
  *
55
  * @return void Early if method is already called.
56
  */
57
  public function add_menu_link() {
58
 
59
+ if ( _has_run( __METHOD__ ) ) return;
 
 
 
60
 
61
  $menu = [
62
  'page_title' => \esc_html__( 'SEO Settings', 'autodescription' ),
63
  'menu_title' => \esc_html__( 'SEO', 'autodescription' ),
64
  'capability' => $this->get_settings_capability(),
65
  'menu_slug' => $this->seo_settings_page_slug,
66
+ 'callback' => [ $this, '_output_settings_wrap' ],
67
  'icon' => 'dashicons-search',
68
  'position' => '90.9001',
69
  ];
93
 
94
  //* Enqueue scripts
95
  \add_action( 'admin_print_scripts-' . $this->seo_settings_page_hook, [ $this, '_init_admin_scripts' ], 11 );
96
+ \add_action( 'load-' . $this->seo_settings_page_hook, [ $this, '_register_seo_settings_meta_boxes' ] );
97
+ }
98
 
99
+ /**
100
+ * Registers the meta boxes early, so WordPress recognizes them for user-settings.
101
+ *
102
+ * @since 4.0.0
103
+ * @see $this->_output_settings_wrap()
104
+ * @access private
105
+ */
106
+ public function _register_seo_settings_meta_boxes() {
107
+ Bridges\SeoSettings::_register_seo_settings_meta_boxes();
108
  }
109
 
110
  /**
111
+ * Outputs the SEO Settings page wrap.
112
  *
113
+ * @since 4.0.0
114
+ * @access private
115
  */
116
+ public function _output_settings_wrap() {
117
 
118
+ \add_action(
119
+ $this->seo_settings_page_hook . '_settings_page_boxes',
120
+ Bridges\SeoSettings::class . '::_output_columns'
121
+ );
122
 
123
+ Bridges\SeoSettings::_output_wrap();
 
 
124
  }
125
 
126
  /**
127
+ * Prepares post edit view, like outputting the fields.
128
  *
129
+ * @since 4.0.0
130
+ *
131
+ * @param string $post_type The current post type.
132
+ * @param \WP_Post $post The Post object.
133
  */
134
+ public function _init_post_edit_view( $post_type, $post ) {
135
+
136
+ if ( ! $this->is_post_edit() ) return;
137
+ if ( ! $this->is_post_type_supported( $post_type ) ) return;
138
+
 
139
  /**
140
+ * @since 2.0.0
141
+ * @param bool $show_seobox Whether to show the SEO meta box.
142
  */
143
+ $show_seobox = (bool) \apply_filters( 'the_seo_framework_seobox_output', true );
144
+
145
+ if ( $show_seobox )
146
+ \add_action(
147
+ 'add_meta_boxes',
148
+ Bridges\PostSettings::class . '::_prepare_meta_box'
149
+ );
150
  }
151
 
152
  /**
153
+ * Prepares term edit view, like outputting the fields.
154
  *
155
+ * @since 4.0.0
 
156
  */
157
+ public function _init_term_edit_view() {
158
+
159
+ if ( ! $this->is_term_edit() ) return;
160
+
161
+ $taxonomy = $this->get_current_taxonomy();
162
+
163
+ if ( ! $this->is_taxonomy_supported( $taxonomy ) ) return;
164
+
165
+ /**
166
+ * @since 2.6.0
167
+ * @param int $priority The metabox term priority.
168
+ * Defaults to a high priority, this box is seen soon below the default edit inputs.
169
+ */
170
+ $priority = (int) \apply_filters( 'the_seo_framework_term_metabox_priority', 0 );
171
+
172
+ \add_action(
173
+ $taxonomy . '_edit_form',
174
+ Bridges\TermSettings::class . '::_prepare_setting_fields',
175
+ $priority,
176
+ 2
177
+ );
178
  }
179
 
180
  /**
181
+ * Outputs notices on SEO setting changes.
182
  *
183
+ * @since 4.0.0
184
  * @access private
 
 
 
 
 
 
 
 
 
 
185
  */
186
+ public static function _do_settings_page_notices() {
187
 
188
+ $tsf = \the_seo_framework();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
 
190
+ $notice = $tsf->get_static_cache( 'settings_notice' );
 
 
 
 
 
 
 
 
 
191
 
192
+ if ( ! $notice ) return;
 
 
 
 
 
 
 
 
 
193
 
194
+ $message = '';
195
+ $type = '';
 
 
 
 
 
 
 
 
196
 
197
+ switch ( $notice ) {
198
+ case 'updated':
199
+ $message = \__( 'SEO settings are saved, and the caches have been flushed.', 'autodescription' );
200
+ $type = 'updated';
201
+ break;
 
 
 
 
 
202
 
203
+ case 'unchanged':
204
+ $message = \__( 'No SEO settings were changed, but the caches have been flushed.', 'autodescription' );
205
+ $type = 'warning';
206
+ break;
 
 
 
 
 
 
207
 
208
+ case 'reset':
209
+ $message = \__( 'SEO settings are reset, and the caches have been flushed.', 'autodescription' );
210
+ $type = 'warning';
211
+ break;
 
 
 
 
 
 
212
 
213
+ case 'error':
214
+ $message = \__( 'An unknown error occurred saving SEO settings.', 'autodescription' );
215
+ $type = 'error';
216
+ break;
217
+ }
 
 
 
 
 
218
 
219
+ $tsf->update_static_cache( 'settings_notice', '' );
 
 
 
 
 
 
 
 
 
220
 
221
+ $message and $tsf->do_dismissible_notice( $message, $type ?: 'updated' );
 
 
 
 
 
 
 
 
 
222
  }
223
 
224
  /**
239
  );
240
  $this->update_static_cache( 'check_seo_plugin_conflicts', 0 );
241
  }
242
+ }
243
 
244
+ /**
245
+ * Setting nav tab wrappers.
246
+ * Outputs Tabs and settings content.
247
+ *
248
+ * @since 2.3.6
249
+ * @since 2.6.0 Refactored.
250
+ * @since 3.1.0 Now prefixes the IDs.
251
+ * @since 4.0.0 Deprecated third parameter, silently.
252
+ *
253
+ * @param string $id The nav-tab ID
254
+ * @param array $tabs The tab content {
255
+ * string tab ID => array : {
256
+ * string name : Tab name.
257
+ * callable callback : Output function.
258
+ * string dashicon : The dashicon to use.
259
+ * mixed args : Optional callback function args.
260
+ * }
261
+ * }
262
+ * @param null $depr Deprecated.
263
+ * @param bool $use_tabs Whether to output tabs, only works when $tabs count is greater than 1.
264
+ */
265
+ public function nav_tab_wrapper( $id, $tabs = [], $depr = null, $use_tabs = true ) {
266
+ Bridges\SeoSettings::_nav_tab_wrapper( $id, $tabs, $use_tabs );
267
  }
268
 
269
  /**
270
+ * Outputs in-post flex navigational wrapper and its content.
271
+ *
272
+ * @since 2.9.0
273
+ * @since 3.0.0: Converted to view.
274
+ * @since 4.0.0: Deprecated third parameter, silently.
275
+ *
276
+ * @param string $id The nav-tab ID
277
+ * @param array $tabs The tab content {
278
+ * string tab ID => array : {
279
+ * string name : Tab name.
280
+ * callable callback : Output function.
281
+ * string dashicon : The dashicon to use.
282
+ * mixed args : Optional callback function args.
283
+ * }
284
+ * }
285
+ * @param null $_depr Deprecated.
286
+ * @param bool $use_tabs Whether to output tabs, only works when $tabs count is greater than 1.
287
  */
288
+ public static function inpost_flex_nav_tab_wrapper( $id, $tabs = [], $_depr = null, $use_tabs = true ) {
289
+ Bridges\PostSettings::_flex_nav_tab_wrapper( $id, $tabs, $use_tabs );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  }
291
 
292
  /**
301
  * @return string Full field name
302
  */
303
  public function get_field_name( $name ) {
304
+ return sprintf( '%s[%s]', THE_SEO_FRAMEWORK_SITE_OPTIONS, $name );
305
  }
306
 
307
  /**
325
  * @return string Full field id
326
  */
327
  public function get_field_id( $id ) {
328
+ return sprintf( '%s[%s]', THE_SEO_FRAMEWORK_SITE_OPTIONS, $id );
329
  }
330
 
331
  /**
334
  * @since 2.2.2
335
  * @uses $this->get_field_id() Constructs id attributes for use in form fields.
336
  *
337
+ * @param string $id Field id base.
338
  * @param boolean $echo Whether to escape echo or just return.
339
  * @return string Full field id
340
  */
352
  *
353
  * @since 2.6.0
354
  * @since 3.0.6 The messages are no longer auto-styled to "strong".
355
+ * @since 4.0.0 Added a tabindex, so keyboard navigation is possible on the "empty" dashicon.
356
  *
357
  * @param string $message The notice message. Expected to be escaped if $escape is false.
358
  * @param string $type The notice type : 'updated', 'error', 'warning'. Expected to be escaped.
359
+ * @param bool $a11y Whether to add an accessibility icon.
360
+ * @param bool $escape Whether to escape the whole output.
361
  * @return string The dismissible error notice.
362
  */
363
  public function generate_dismissible_notice( $message = '', $type = 'updated', $a11y = true, $escape = true ) {
367
  //* Make sure the scripts are loaded.
368
  $this->init_admin_scripts();
369
 
370
+ \The_SEO_Framework\Builders\Scripts::enqueue();
 
 
371
 
372
  if ( 'warning' === $type )
373
  $type = 'notice-warning';
375
  $a11y = $a11y ? 'tsf-show-icon' : '';
376
 
377
  return vsprintf(
378
+ '<div class="notice %s tsf-notice %s"><p>%s</p>%s</div>',
379
  [
380
  \esc_attr( $type ),
381
  ( $a11y ? 'tsf-show-icon' : '' ),
382
+ ( $escape ? \esc_html( $message ) : $message ),
383
  sprintf(
384
+ '<a class="hide-if-no-tsf-js tsf-dismiss" title="%s" tabindex=0></a>',
385
+ \esc_attr__( 'Dismiss this notice', 'autodescription' )
 
386
  ),
 
387
  ]
388
  );
389
  }
393
  *
394
  * @since 2.7.0
395
  *
396
+ * @param string $message The notice message. Expected to be escaped if $escape is false.
397
+ * @param string $type The notice type : 'updated', 'error', 'warning'. Expected to be escaped.
398
+ * @param bool $a11y Whether to add an accessibility icon.
399
+ * @param bool $escape Whether to escape the whole output.
400
  */
401
  public function do_dismissible_notice( $message = '', $type = 'updated', $a11y = true, $escape = true ) {
402
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- use $escape
403
+ echo $this->generate_dismissible_notice( $message, $type, (bool) $a11y, (bool) $escape );
404
  }
405
 
406
  /**
411
  * @see $this->do_dismissible_sticky_notice()
412
  * @uses THE_SEO_FRAMEWORK_UPDATES_CACHE
413
  * @todo make this do something.
414
+ * @ignore
415
  * NOTE: This method is a placeholder.
416
  *
417
  * @param string $message The notice message. Expected to be escaped if $escape is false.
418
  * @param string $key The notice key. Must be unique and tied to the stored updates cache option.
419
+ * @param array $args : {
420
  * 'type' => string Optional. The notification type. Default 'updated'.
421
  * 'a11y' => bool Optional. Whether to enable accessibility. Default true.
422
  * 'escape' => bool Optional. Whether to escape the $message. Default true.
425
  * }
426
  * @return string The dismissible error notice.
427
  */
428
+ public function generate_dismissible_sticky_notice( $message, $key, $args = [] ) { // phpcs:ignore -- unused.
429
  return '';
430
  }
431
 
434
  *
435
  * @since 2.9.3
436
  * @uses $this->generate_dismissible_sticky_notice()
437
+ * @ignore
438
  *
439
  * @param string $message The notice message. Expected to be escaped if $escape is false.
440
  * @param string $key The notice key. Must be unique and tied to the stored updates cache option.
441
+ * @param array $args : {
442
  * 'type' => string Optional. The notification type. Default 'updated'.
443
  * 'a11y' => bool Optional. Whether to enable accessibility. Default true.
444
  * 'escape' => bool Optional. Whether to escape the $message. Default true.
445
  * 'color' => string Optional. If filled in, it will output the selected color. Default ''.
446
  * 'icon' => string Optional. If filled in, it will output the selected icon. Default ''.
447
  * }
 
448
  */
449
  public function do_dismissible_sticky_notice( $message, $key, $args = [] ) {
450
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- use $args['escape']
451
+ echo $this->generate_dismissible_sticky_notice( $message, $key, $args );
452
  }
453
 
454
  /**
485
  *
486
  * @param string $content Content to be wrapped in the description wrap.
487
  * @param bool $block Whether to wrap the content in <p> tags.
 
488
  */
489
  public function description( $content, $block = true ) {
490
  $this->description_noesc( \esc_html( $content ), $block );
497
  *
498
  * @param string $content Content to be wrapped in the description wrap. Expected to be escaped.
499
  * @param bool $block Whether to wrap the content in <p> tags.
 
500
  */
501
  public function description_noesc( $content, $block = true ) {
502
  $output = '<span class="description">' . $content . '</span>';
503
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- Method clearly states it's not escaped.
504
+ echo $block ? '<p>' . $output . '</p>' : $output;
505
  }
506
 
507
  /**
512
  *
513
  * @param string $content Content to be wrapped in the attention wrap.
514
  * @param bool $block Whether to wrap the content in <p> tags.
 
515
  */
516
  public function attention( $content, $block = true ) {
517
  $this->attention_noesc( \esc_html( $content ), $block );
524
  *
525
  * @param string $content Content to be wrapped in the attention wrap. Expected to be escaped.
526
  * @param bool $block Whether to wrap the content in <p> tags.
 
527
  */
528
  public function attention_noesc( $content, $block = true ) {
529
  $output = '<span class="attention">' . $content . '</span>';
530
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- Method clearly states it's not escaped.
531
+ echo $block ? '<p>' . $output . '</p>' : $output;
532
  }
533
 
534
  /**
539
  *
540
  * @param string $content Content to be wrapped in the wrap. Expected to be escaped.
541
  * @param bool $block Whether to wrap the content in <p> tags.
 
542
  */
543
  public function attention_description( $content, $block = true ) {
544
  $this->attention_description_noesc( \esc_html( $content ), $block );
551
  *
552
  * @param string $content Content to be wrapped in the wrap. Expected to be escaped.
553
  * @param bool $block Whether to wrap the content in <p> tags.
 
554
  */
555
  public function attention_description_noesc( $content, $block = true ) {
556
  $output = '<span class="description attention">' . $content . '</span>';
557
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- Method clearly states it's not escaped.
558
+ echo $block ? '<p>' . $output . '</p>' : $output;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
  }
560
 
561
  /**
562
  * Echo or return a chechbox fields wrapper.
563
  *
564
+ * This method does NOT escape.
565
+ *
566
  * @since 2.6.0
567
  *
568
  * @param string $input The input to wrap. Should already be escaped.
569
+ * @param bool $echo Whether to escape echo or just return.
570
  * @return string|void Wrapped $input.
571
  */
572
  public function wrap_fields( $input = '', $echo = false ) {
575
  $input = implode( PHP_EOL, $input );
576
 
577
  if ( $echo ) {
578
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- Escape your $input prior!
579
+ echo '<div class="tsf-fields">' . $input . '</div>';
580
  } else {
581
  return '<div class="tsf-fields">' . $input . '</div>';
582
  }
583
  }
584
 
585
+ /**
586
+ * Makes either simple or JSON-encoded data-* attributes for HTML elements.
587
+ *
588
+ * @since 4.0.0
589
+ * @internal
590
+ *
591
+ * @param array $data : {
592
+ * string $k => mixed $v
593
+ * }
594
+ * @return string The HTML data attributes, with added space to the start.
595
+ */
596
+ public function make_data_attributes( array $data ) {
597
+
598
+ $ret = [];
599
+
600
+ foreach ( $data as $k => $v ) {
601
+ if ( ! is_scalar( $v ) ) {
602
+ $ret[] = sprintf(
603
+ 'data-%s="%s"',
604
+ strtolower( preg_replace(
605
+ '/([A-Z])/',
606
+ '-$1',
607
+ preg_replace( '/[^a-z0-9_\-]/i', '', $k )
608
+ ) ), // dash case.
609
+ htmlspecialchars( json_encode( $v, JSON_UNESCAPED_SLASHES ), ENT_COMPAT, 'UTF-8' )
610
+ );
611
+ } else {
612
+ $ret[] = sprintf(
613
+ 'data-%s="%s"',
614
+ strtolower( preg_replace(
615
+ '/([A-Z])/',
616
+ '-$1',
617
+ preg_replace( '/[^a-z0-9_\-]/i', '', $k )
618
+ ) ), // dash case.
619
+ \esc_attr( $v )
620
+ );
621
+ }
622
+ }
623
+
624
+ return ' ' . implode( ' ', $ret );
625
+ }
626
+
627
  /**
628
  * Returns a chechbox wrapper.
629
  *
656
  * @since 3.1.0
657
  *
658
  * @param array $args : {
659
+ * string $id The option name, used as field ID.
660
+ * string $index The option index, used when the option is an array.
661
+ * string $label The checkbox label description, placed inline of the checkbox.
662
+ * string $description The checkbox additional description, placed underneat.
663
+ * bool $escape Whether to enable escaping of the $label and $description.
664
+ * bool $disabled Whether to disable the checkbox field.
665
+ * bool $default Whether to display-as-default. This is autodetermined when no $index is set.
666
+ * bool $warned Whether to warn the checkbox field value.
667
  * }
668
  * @return string HTML checkbox output.
669
  */
670
  public function make_checkbox_array( array $args = [] ) {
671
 
672
+ $args = array_merge(
673
+ [
674
+ 'id' => '',
675
+ 'index' => '',
676
+ 'label' => '',
677
+ 'description' => '',
678
+ 'escape' => true,
679
+ 'disabled' => false,
680
+ 'default' => false,
681
+ 'warned' => false,
682
+ ],
683
+ $args
684
+ );
685
 
686
  if ( $args['escape'] ) {
687
  $args['description'] = \esc_html( $args['description'] );
688
  $args['label'] = \esc_html( $args['label'] );
689
  }
690
 
691
+ $index = $this->s_field_id( $args['index'] ?: '' );
692
 
693
  $field_id = $field_name = \esc_attr( sprintf(
694
  '%s%s',
742
  return $output;
743
  }
744
 
745
+ /**
746
+ * Returns a HTML select form elements for qubit options: -1, 0, or 1.
747
+ * Does not support "multiple" field selections.
748
+ *
749
+ * @since 4.0.0
750
+ *
751
+ * @param array $args : {
752
+ * string $id The select field ID.
753
+ * string $class The div wrapper class.
754
+ * string $name The option name.
755
+ * int|string $default The current option value.
756
+ * array $options The select option values : { value => name }
757
+ * string $label The option label.
758
+ * string $required Whether the field must be required.
759
+ * array $data The select field data. Sub-items are expected to be escaped if they're not an array.
760
+ * array $info Extra info field data.
761
+ * }
762
+ * @return string The option field.
763
+ */
764
+ public function make_single_select_form( array $args ) {
765
+
766
+ $defaults = [
767
+ 'id' => '',
768
+ 'class' => '',
769
+ 'name' => '',
770
+ 'default' => '',
771
+ 'options' => [],
772
+ 'label' => '',
773
+ 'required' => false,
774
+ 'data' => [],
775
+ 'info' => [],
776
+ ];
777
+
778
+ $args = array_merge( $defaults, $args );
779
+
780
+ // The walk below destroys the option array. As such, we assigned a new value.
781
+ $html_options = $args['options'];
782
+
783
+ array_walk(
784
+ $html_options,
785
+ /**
786
+ * @param string $name The option name. Passed by reference, returned as the HTML option item.
787
+ * @param mixed $value
788
+ * @param mixed $default
789
+ */
790
+ function( &$name, $value, $default ) {
791
+ $name = sprintf(
792
+ '<option value="%s"%s>%s</option>',
793
+ \esc_attr( $value ),
794
+ (string) $value === (string) $default ? ' selected' : '',
795
+ \esc_html( $name )
796
+ );
797
+ },
798
+ $args['default']
799
+ );
800
+
801
+ return vsprintf(
802
+ sprintf( '<div class="%s">%s</div>',
803
+ \esc_attr( $args['class'] ),
804
+ ( \is_rtl() ? '%2$s%1$s%3$s' : '%1$s%2$s%3$s' )
805
+ ),
806
+ [
807
+ $args['label'] ? sprintf(
808
+ '<label for=%s>%s</label> ', // NOTE: extra space!
809
+ $this->s_field_id( $args['id'] ),
810
+ \esc_html( $args['label'] )
811
+ ) : '',
812
+ $args['info'] ? ' ' . $this->make_info(
813
+ $args['info'][0],
814
+ isset( $args['info'][1] ) ? $args['info'][1] : '',
815
+ false
816
+ ) : '',
817
+ vsprintf(
818
+ '<select id=%s name=%s %s %s>%s</select>',
819
+ [
820
+ $this->s_field_id( $args['id'] ),
821
+ \esc_attr( $args['name'] ),
822
+ $args['required'] ? 'required' : '',
823
+ $args['data'] ? $this->make_data_attributes( $args['data'] ) : '',
824
+ implode( $html_options ),
825
+ ]
826
+ ),
827
+ ]
828
+ );
829
+ }
830
+
831
  /**
832
  * Return a wrapped question mark.
833
  *
834
  * @since 2.6.0
835
  * @since 3.0.0 Links are now no longer followed, referred or bound to opener.
836
+ * @since 4.0.0 Now adds a tabindex to the span tag, so you can focus it using keyboard navigation.
837
  *
838
  * @param string $description The descriptive on-hover title.
839
+ * @param string $link The non-escaped link.
840
+ * @param bool $echo Whether to echo or return.
841
  * @return string HTML checkbox output if $echo is false.
842
  */
843
  public function make_info( $description = '', $link = '', $echo = true ) {
845
  if ( $link ) {
846
  $output = sprintf(
847
  '<a href="%1$s" class="tsf-tooltip-item tsf-help" target="_blank" rel="nofollow noreferrer noopener" title="%2$s" data-desc="%2$s">[?]</a>',
848
+ \esc_url( $link, [ 'https', 'http' ] ),
849
  \esc_attr( $description )
850
  );
851
  } else {
852
  $output = sprintf(
853
+ '<span class="tsf-tooltip-item tsf-help" title="%1$s" data-desc="%1$s" tabindex=0>[?]</span>',
854
  \esc_attr( $description )
855
  );
856
  }
858
  $output = sprintf( '<span class="tsf-tooltip-wrap">%s</span>', $output );
859
 
860
  if ( $echo ) {
861
+ // phpcs:ignore, WordPress.Security.EscapeOutput
862
+ echo $output;
863
  } else {
864
  return $output;
865
  }
866
  }
867
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
868
  /**
869
  * Returns the HTML class wrap for default Checkbox options.
870
  *
957
  * @since 2.3.4
958
  * @since 3.1.0 Deprecated second parameter.
959
  *
960
+ * @param string $key The option name which returns boolean.
961
+ * @param string $deprecated Deprecated. Used to be the settings field.
962
+ * @param bool $wrap Whether to wrap the class name in `class="%s"`
963
+ * @param bool $echo Whether to echo or return the output.
964
  * @return string Empty on echo or the class name with an optional wrapper.
965
  */
966
  public function is_conditional_checked( $key, $deprecated = '', $wrap = true, $echo = true ) {
992
  }
993
  }
994
 
995
+ /**
996
+ * Returns the SEO Bar.
997
+ *
998
+ * @since 4.0.0
999
+ * @uses \The_SEO_Framework\Interpreters\SeoBar::generate_bar();
1000
+ *
1001
+ * @param array $query : {
1002
+ * int $id : Required. The current post or term ID.
1003
+ * string $taxonomy : Optional. If not set, this will interpret it as a post.
1004
+ * string $post_type : Optional. If not set, this will be automatically filled.
1005
+ * This parameter is ignored for taxonomies.
1006
+ * }
1007
+ * @return string The generated SEO bar, in HTML.
1008
+ */
1009
+ public function get_generated_seo_bar( array $query ) {
1010
+ return Interpreters\SeoBar::generate_bar( $query );
1011
+ }
1012
+
1013
  /**
1014
  * Returns social image uploader form button.
1015
  * Also registers additional i18n strings for JS.
1016
  *
1017
  * @since 2.8.0
1018
  * @since 3.1.0 No longer prepares media l10n data.
1019
+ * @since 4.0.0 Now adds a media preview dispenser.
1020
  *
1021
  * @param string $input_id Required. The HTML input id to pass URL into.
1022
  * @return string The image uploader button.
1029
  $s_input_id = \esc_attr( $input_id );
1030
 
1031
  $content = vsprintf(
1032
+ '<button type=button data-href="%s" class="tsf-set-image-button button button-primary button-small" title="%s" id="%s-select"
1033
+ %s>%s</button>',
1034
  [
1035
  \esc_url( \get_upload_iframe_src( 'image', $this->get_the_real_ID() ) ),
1036
  \esc_attr_x( 'Select social image', 'Button hover', 'autodescription' ),
1037
  $s_input_id,
1038
+ $this->make_data_attributes( [
1039
+ 'inputId' => $s_input_id,
1040
+ 'inputType' => 'social',
1041
+ 'width' => 1200,
1042
+ 'height' => 630,
1043
+ 'minWidth' => 200,
1044
+ 'minHeight' => 200,
1045
+ 'flex' => true,
1046
+ ] ),
1047
  \esc_html__( 'Select Image', 'autodescription' ),
1048
  ]
1049
  );
1050
 
1051
+ $content .= vsprintf(
1052
+ '<span class="tsf-tooltip-wrap"><span id="%1$s-preview" class="tsf-image-preview tsf-tooltip-item dashicons dashicons-format-image" data-for="%1$s" tabindex=0></span></span>',
1053
+ $s_input_id
1054
+ );
1055
+
1056
  return $content;
1057
  }
1058
 
1062
  *
1063
  * @since 3.0.0
1064
  * @since 3.1.0 No longer prepares media l10n data.
1065
+ * @since 4.0.0 Now adds a media preview dispenser.
1066
  *
1067
  * @param string $input_id Required. The HTML input id to pass URL into.
1068
  * @return string The image uploader button.
1075
  $s_input_id = \esc_attr( $input_id );
1076
 
1077
  $content = vsprintf(
1078
+ '<button type=button data-href="%s" class="tsf-set-image-button button button-primary button-small" title="%s" id="%s-select"
1079
+ %s>%s</button>',
1080
  [
1081
  \esc_url( \get_upload_iframe_src( 'image', $this->get_the_real_ID() ) ),
1082
+ '', // Redundant
1083
  $s_input_id,
1084
+ $this->make_data_attributes( [
1085
+ 'inputId' => $s_input_id,
1086
+ 'inputType' => 'logo',
1087
+ 'width' => 512,
1088
+ 'height' => 512,
1089
+ 'minWidth' => 112,
1090
+ 'minHeight' => 112,
1091
+ 'flex' => true,
1092
+ ] ),
1093
  \esc_html__( 'Select Logo', 'autodescription' ),
1094
  ]
1095
  );
1096
 
1097
+ $content .= vsprintf(
1098
+ '<span class="tsf-tooltip-wrap"><span id="%1$s-preview" class="tsf-image-preview tsf-tooltip-item dashicons dashicons-format-image" data-for="%1$s" tabindex=0></span></span>',
1099
+ $s_input_id
1100
+ );
1101
+
1102
  return $content;
1103
  }
1104
 
1108
  * @since 3.0.4
1109
  */
1110
  public function output_js_title_elements() {
1111
+ echo '<span id=tsf-title-reference style=display:none></span>';
1112
+ echo '<span id=tsf-title-offset class=hide-if-no-tsf-js></span>';
1113
+ echo '<span id=tsf-title-placeholder class=hide-if-no-tsf-js></span>';
1114
+ echo '<span id=tsf-title-placeholder-prefix class=hide-if-no-tsf-js></span>';
1115
  }
1116
 
1117
  /**
1132
  * 3. The whole output is now hidden from no-js.
1133
  *
1134
  * @param string $for The input ID it's for.
1135
+ * @param string $depr The initial value for no-JS. Deprecated.
1136
  * @param bool $display Whether to display the counter. (options page gimmick)
1137
  */
1138
+ public function output_character_counter_wrap( $for, $depr = '', $display = true ) {
1139
  vprintf(
1140
+ '<div class="tsf-counter-wrap hide-if-no-tsf-js" %s><span class="description tsf-counter" title="%s">%s</span><span class="tsf-ajax"></span></div>',
1141
  [
1142
  ( $display ? '' : 'style="display:none;"' ),
1143
+ \esc_attr__( 'Click to change the counter type', 'autodescription' ),
1144
  sprintf(
1145
  /* translators: %s = number */
1146
  \esc_html__( 'Characters Used: %s', 'autodescription' ),
1165
  */
1166
  public function output_pixel_counter_wrap( $for, $type, $display = true ) {
1167
  vprintf(
1168
+ '<div class="tsf-pixel-counter-wrap hide-if-no-tsf-js" %s>%s%s</div>',
1169
  [
1170
  ( $display ? '' : 'style="display:none;"' ),
1171
  sprintf(
1172
  '<div id="%s_pixels" class="tsf-tooltip-wrap">%s</div>',
1173
  \esc_attr( $for ),
1174
+ '<span class="tsf-pixel-counter-bar tsf-tooltip-item" aria-label="" data-desc="" tabindex=0><span class="tsf-pixel-counter-fluid"></span></span>'
1175
  ),
1176
  sprintf(
1177
  '<div class="tsf-pixel-shadow-wrap"><span class="tsf-pixel-counter-shadow tsf-%s-pixel-counter-shadow"></span></div>',
1180
  ]
1181
  );
1182
  }
1183
+
1184
+ /**
1185
+ * Calculates the social title and description placeholder values.
1186
+ * This is intricated, voluminous, and convoluted; but, there's no other way :(
1187
+ *
1188
+ * @since 4.0.0
1189
+ * @access private
1190
+ *
1191
+ * @param array $args An array of 'id' and 'taxonomy' values.
1192
+ * @param string $for The screen it's for. Accepts 'edit' and 'settings'.
1193
+ * @return array An array social of titles and descriptions.
1194
+ */
1195
+ public function _get_social_placeholders( array $args, $for = 'edit' ) {
1196
+
1197
+ $desc_from_custom_field = $this->get_description_from_custom_field( $args );
1198
+
1199
+ if ( 'settings' === $for ) {
1200
+ $pm_edit_og_title = $args['id'] ? $this->get_post_meta_item( '_open_graph_title', $args['id'] ) : '';
1201
+ $pm_edit_og_desc = $args['id'] ? $this->get_post_meta_item( '_open_graph_description', $args['id'] ) : '';
1202
+ $pm_edit_tw_title = $args['id'] ? $this->get_post_meta_item( '_twitter_title', $args['id'] ) : '';
1203
+ $pm_edit_tw_desc = $args['id'] ? $this->get_post_meta_item( '_twitter_description', $args['id'] ) : '';
1204
+
1205
+ // Gets custom fields from SEO settings.
1206
+ $home_og_title = $this->get_option( 'homepage_og_title' );
1207
+ $home_og_desc = $this->get_option( 'homepage_og_description' );
1208
+
1209
+ //! OG title generator falls back to meta input. The description does not.
1210
+ $og_tit_placeholder = $pm_edit_og_title
1211
+ ?: $this->get_generated_open_graph_title( $args );
1212
+ $og_desc_placeholder = $pm_edit_og_desc
1213
+ ?: $desc_from_custom_field
1214
+ ?: $this->get_generated_open_graph_description( $args );
1215
+
1216
+ //! TW title generator falls back to meta input. The description does not.
1217
+ $tw_tit_placeholder = $pm_edit_tw_title
1218
+ ?: $home_og_title
1219
+ ?: $pm_edit_og_title
1220
+ ?: $this->get_generated_twitter_title( $args );
1221
+ $tw_desc_placeholder = $pm_edit_tw_desc
1222
+ ?: $home_og_desc
1223
+ ?: $pm_edit_og_desc
1224
+ ?: $desc_from_custom_field
1225
+ ?: $this->get_generated_twitter_description( $args );
1226
+ } elseif ( 'edit' === $for ) {
1227
+ if ( ! $args['taxonomy'] ) {
1228
+ if ( $this->is_static_frontpage( $args['id'] ) ) {
1229
+ // Gets custom fields from SEO settings.
1230
+ $home_desc = $this->get_option( 'homepage_description' );
1231
+
1232
+ $home_og_title = $this->get_option( 'homepage_og_title' );
1233
+ $home_og_desc = $this->get_option( 'homepage_og_description' );
1234
+ $home_tw_title = $this->get_option( 'homepage_twitter_title' );
1235
+ $home_tw_desc = $this->get_option( 'homepage_twitter_description' );
1236
+
1237
+ // Gets custom fields from page.
1238
+ $custom_og_title = $this->get_post_meta_item( '_open_graph_title', $args['id'] );
1239
+ $custom_og_desc = $this->get_post_meta_item( '_open_graph_description', $args['id'] );
1240
+
1241
+ //! OG title generator falls back to meta input. The description does not.
1242
+ $og_tit_placeholder = $home_og_title
1243
+ ?: $this->get_generated_open_graph_title( $args );
1244
+ $og_desc_placeholder = $home_og_desc
1245
+ ?: $home_desc
1246
+ ?: $desc_from_custom_field
1247
+ ?: $this->get_generated_open_graph_description( $args );
1248
+
1249
+ //! TW title generator falls back to meta input. The description does not.
1250
+ $tw_tit_placeholder = $home_tw_title
1251
+ ?: $home_og_title
1252
+ ?: $custom_og_title
1253
+ ?: $this->get_generated_twitter_title( $args );
1254
+ $tw_desc_placeholder = $home_tw_desc
1255
+ ?: $home_og_desc
1256
+ ?: $custom_og_desc
1257
+ ?: $home_desc
1258
+ ?: $desc_from_custom_field
1259
+ ?: $this->get_generated_twitter_description( $args );
1260
+ } else {
1261
+ // Gets custom fields.
1262
+ $custom_og_title = $this->get_post_meta_item( '_open_graph_title', $args['id'] );
1263
+ $custom_og_desc = $this->get_post_meta_item( '_open_graph_description', $args['id'] );
1264
+
1265
+ //! OG title generator falls back to meta input. The description does not.
1266
+ $og_tit_placeholder = $this->get_generated_open_graph_title( $args );
1267
+ $og_desc_placeholder = $desc_from_custom_field
1268
+ ?: $this->get_generated_open_graph_description( $args );
1269
+
1270
+ //! TW title generator falls back to meta input. The description does not.
1271
+ $tw_tit_placeholder = $custom_og_title
1272
+ ?: $this->get_generated_twitter_title( $args );
1273
+ $tw_desc_placeholder = $custom_og_desc
1274
+ ?: $desc_from_custom_field
1275
+ ?: $this->get_generated_twitter_description( $args );
1276
+ }
1277
+ } else {
1278
+ $meta = $this->get_term_meta( $args['id'] );
1279
+
1280
+ //! OG title generator falls back to meta input. The description does not.
1281
+ $og_tit_placeholder = $this->get_generated_open_graph_title( $args );
1282
+ $og_desc_placeholder = $desc_from_custom_field
1283
+ ?: $this->get_generated_open_graph_description( $args );
1284
+
1285
+ //! TW title generator falls back to meta input. The description does not.
1286
+ $tw_tit_placeholder = $meta['og_title']
1287
+ ?: $og_tit_placeholder;
1288
+ $tw_desc_placeholder = $meta['og_description']
1289
+ ?: $desc_from_custom_field
1290
+ ?: $this->get_generated_twitter_description( $args );
1291
+ }
1292
+ } else {
1293
+ $og_tit_placeholder = '';
1294
+ $tw_tit_placeholder = '';
1295
+ $og_desc_placeholder = '';
1296
+ $tw_desc_placeholder = '';
1297
+ }
1298
+
1299
+ return [
1300
+ 'title' => [
1301
+ 'og' => $og_tit_placeholder,
1302
+ 'twitter' => $tw_tit_placeholder,
1303
+ ],
1304
+ 'description' => [
1305
+ 'og' => $og_desc_placeholder,
1306
+ 'twitter' => $tw_desc_placeholder,
1307
+ ],
1308
+ ];
1309
+ }
1310
  }
inc/classes/bridges/index.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * No no, don't speak! For some moments in life there are no words.
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
+ */
inc/classes/bridges/listedit.class.php ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Bridges\ListEdit
4
+ * @subpackage The_SEO_Framework\Admin\Edit\List
5
+ */
6
+
7
+ namespace The_SEO_Framework\Bridges;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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
+ * Prepares the List Edit view interface.
30
+ *
31
+ * @since 4.0.0
32
+ * @access protected
33
+ * @internal
34
+ * @final Can't be extended.
35
+ */
36
+ final class ListEdit extends ListTable {
37
+
38
+ /**
39
+ * @since 4.0.0
40
+ * @var string The column name.
41
+ */
42
+ private $column_name = 'tsf-quick-edit';
43
+
44
+ /**
45
+ * Constructor, sets column name and calls parent.
46
+ *
47
+ * @since 4.0.0
48
+ */
49
+ public function __construct() {
50
+ parent::__construct();
51
+
52
+ \add_filter( 'hidden_columns', [ $this, '_hide_quick_edit_column' ], 10, 1 );
53
+ \add_action( 'current_screen', [ $this, '_prepare_edit_box' ] );
54
+ }
55
+
56
+ /**
57
+ * Prepares the quick/bulk edit output.
58
+ *
59
+ * @since 4.0.0
60
+ * @access private
61
+ *
62
+ * @param \WP_Screen|string $screen \WP_Screen
63
+ */
64
+ public function _prepare_edit_box( $screen ) {
65
+
66
+ $taxonomy = isset( $screen->taxonomy ) ? $screen->taxonomy : '';
67
+
68
+ if ( ! $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".
72
+ \add_action( 'bulk_edit_custom_box', [ $this, '_display_bulk_edit_fields' ], 10, 2 );
73
+ }
74
+ \add_action( 'quick_edit_custom_box', [ $this, '_display_quick_edit_fields' ], 10, 3 );
75
+ }
76
+
77
+ /**
78
+ * Permanently hides quick/bulk-edit column.
79
+ *
80
+ * @since 4.0.0
81
+ * @access private
82
+ *
83
+ * @param array $hidden The existing hidden columns.
84
+ * @return array $columns the column data
85
+ */
86
+ public function _hide_quick_edit_column( $hidden ) {
87
+ $hidden[] = $this->column_name;
88
+ return $hidden;
89
+ }
90
+
91
+ /**
92
+ * Adds hidden column to access quick/bulk-edit.
93
+ * This column is a dummy, but it's required to display quick/bulk edit items.
94
+ *
95
+ * @since 4.0.0
96
+ * @access private
97
+ * @abstract
98
+ *
99
+ * @param array $columns The existing columns
100
+ * @return array $columns the column data
101
+ */
102
+ public function _add_column( $columns ) {
103
+ // Don't set a title, otherwise it's displayed in the screen settings.
104
+ $columns[ $this->column_name ] = '';
105
+ return $columns;
106
+ }
107
+
108
+ /**
109
+ * Displays the SEO bulk edit fields.
110
+ *
111
+ * @since 4.0.0
112
+ * @access private
113
+ *
114
+ * @param string $column_name Name of the column to edit.
115
+ * @param string $post_type The post type slug, or current screen name if this is a taxonomy list table.
116
+ * @param string $taxonomy The taxonomy name, if any.
117
+ */
118
+ public function _display_bulk_edit_fields( $column_name, $post_type, $taxonomy = '' ) {
119
+
120
+ if ( $this->column_name !== $column_name ) return;
121
+
122
+ // phpcs:ignore, Generic.CodeAnalysis.EmptyStatement -- For the future, when WordPress Core decides.
123
+ if ( $taxonomy ) {
124
+ // Not yet.
125
+ } else {
126
+ \the_seo_framework()->get_view( 'list/bulk-post' );
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Displays the SEO quick edit fields.
132
+ *
133
+ * @since 4.0.0
134
+ * @access private
135
+ *
136
+ * @param string $column_name Name of the column to edit.
137
+ * @param string $post_type The post type slug, or current screen name if this is a taxonomy list table.
138
+ * @param string $taxonomy The taxonomy name, if any.
139
+ */
140
+ public function _display_quick_edit_fields( $column_name, $post_type, $taxonomy = '' ) {
141
+
142
+ if ( $this->column_name !== $column_name ) return;
143
+
144
+ if ( $taxonomy ) {
145
+ \the_seo_framework()->get_view( 'list/quick-term' );
146
+ } else {
147
+ \the_seo_framework()->get_view( 'list/quick-post' );
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Outputs the quick edit data for posts and pages.
153
+ *
154
+ * @since 4.0.0
155
+ * @access private
156
+ * @abstract
157
+ *
158
+ * @param string $column_name The name of the column to display.
159
+ * @param int $post_id The current post ID.
160
+ */
161
+ public function _output_column_contents_for_post( $column_name, $post_id ) {
162
+
163
+ if ( $this->column_name !== $column_name ) return;
164
+ if ( ! \current_user_can( 'edit_post', $post_id ) ) return;
165
+
166
+ $tsf = \the_seo_framework();
167
+
168
+ $query = [ 'id' => $post_id ];
169
+
170
+ $r_defaults = $tsf->robots_meta(
171
+ $query,
172
+ \The_SEO_Framework\ROBOTS_IGNORE_SETTINGS | \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION
173
+ );
174
+
175
+ $meta = $tsf->get_post_meta( $post_id );
176
+
177
+ // NB: The indexes correspond to `autodescription-list[index]` field input names.
178
+ $data = [
179
+ 'canonical' => [
180
+ 'value' => $meta['_genesis_canonical_uri'],
181
+ ],
182
+ 'noindex' => [
183
+ 'default' => empty( $r_defaults['noindex'] ) ? 'index' : 'noindex',
184
+ 'value' => $meta['_genesis_noindex'],
185
+ ],
186
+ 'nofollow' => [
187
+ 'default' => empty( $r_defaults['nofollow'] ) ? 'follow' : 'nofollow',
188
+ 'value' => $meta['_genesis_nofollow'],
189
+ ],
190
+ 'noarchive' => [
191
+ 'default' => empty( $r_defaults['noarchive'] ) ? 'archive' : 'noarchive',
192
+ 'value' => $meta['_genesis_noarchive'],
193
+ ],
194
+ 'redirect' => [
195
+ 'value' => $meta['redirect'],
196
+ ],
197
+ ];
198
+
199
+ printf(
200
+ '<span class=hidden id=%s data-le="%s"></span>',
201
+ sprintf( 'tsfLeData[%s]', (int) $post_id ),
202
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- esc_attr is too aggressive.
203
+ htmlspecialchars( json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_FORCE_OBJECT ), ENT_QUOTES, 'UTF-8' )
204
+ );
205
+ }
206
+
207
+ /**
208
+ * Returns the quick edit data for terms.
209
+ *
210
+ * @since 4.0.0
211
+ * @access private
212
+ * @abstract
213
+ * @NOTE Unlike `_output_column_post_data()`, this is a filter callback.
214
+ * Because of this, the first parameter is a useless string, which must be extended.
215
+ * Discrepancy: https://core.trac.wordpress.org/ticket/33521
216
+ *
217
+ * @param string $string Blank string.
218
+ * @param string $column_name Name of the column.
219
+ * @param string $term_id Term ID.
220
+ * @return string
221
+ */
222
+ public function _output_column_contents_for_term( $string, $column_name, $term_id ) {
223
+
224
+ if ( $this->column_name !== $column_name ) return $string;
225
+ if ( ! \current_user_can( 'edit_term', $term_id ) ) return $string;
226
+
227
+ $tsf = \the_seo_framework();
228
+
229
+ $r_defaults = $tsf->robots_meta(
230
+ [
231
+ 'id' => $term_id,
232
+ 'taxonomy' => $this->taxonomy,
233
+ ],
234
+ \The_SEO_Framework\ROBOTS_IGNORE_SETTINGS | \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION
235
+ );
236
+
237
+ $meta = $tsf->get_term_meta( $term_id );
238
+
239
+ // NB: The indexes correspond to `autodescription-list[index]` field input names.
240
+ $data = [
241
+ 'canonical' => [
242
+ 'value' => $meta['canonical'],
243
+ ],
244
+ 'noindex' => [
245
+ 'default' => empty( $r_defaults['noindex'] ) ? 'index' : 'noindex',
246
+ 'value' => $meta['noindex'],
247
+ ],
248
+ 'nofollow' => [
249
+ 'default' => empty( $r_defaults['nofollow'] ) ? 'follow' : 'nofollow',
250
+ 'value' => $meta['nofollow'],
251
+ ],
252
+ 'noarchive' => [
253
+ 'default' => empty( $r_defaults['noarchive'] ) ? 'archive' : 'noarchive',
254
+ 'value' => $meta['noarchive'],
255
+ ],
256
+ 'redirect' => [
257
+ 'value' => $meta['redirect'],
258
+ ],
259
+ ];
260
+
261
+ $container = sprintf(
262
+ '<span class=hidden id=%s data-le="%s"></span>',
263
+ sprintf( 'tsfLeData[%s]', (int) $term_id ),
264
+ htmlspecialchars( json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_FORCE_OBJECT ), ENT_QUOTES, 'UTF-8' )
265
+ );
266
+
267
+ return $string . $container;
268
+ }
269
+ }
inc/classes/bridges/listtable.class.php ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Bridges\ListTable
4
+ */
5
+
6
+ namespace The_SEO_Framework\Bridges;
7
+
8
+ /**
9
+ * The SEO Framework plugin
10
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (https://cyberwire.nl/)
11
+ *
12
+ * This program is free software: you can redistribute it and/or modify
13
+ * it under the terms of the GNU General Public License version 3 as published
14
+ * by the Free Software Foundation.
15
+ *
16
+ * This program is distributed in the hope that it will be useful,
17
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ * GNU General Public License for more details.
20
+ *
21
+ * You should have received a copy of the GNU General Public License
22
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
23
+ */
24
+
25
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
26
+
27
+ /**
28
+ * Prepares the list table action sequence.
29
+ *
30
+ * @TODO optimize: Every class extending this will invoke running the exact same
31
+ * sequence, over and over again. This class was created because I noticed the
32
+ * classes extending this all did the same. This isn't optimal, but it's a drop-in
33
+ * solution to thin the codebase. Moreover, we load this+X classes, while those X
34
+ * classes may not even do anything at all when this sequence fails to invoke the
35
+ * abstractable methods. We're talking about only 0.01ms (fail) to 0.05ms (success)
36
+ * of extra load time, every single admin request, however, because we memoize deep checks.
37
+ * Totally negligible.
38
+ *
39
+ * @since 4.0.0
40
+ * @access private
41
+ * @abstract
42
+ */
43
+ abstract class ListTable {
44
+ use \The_SEO_Framework\Traits\Enclose_Core_Final;
45
+
46
+ /**
47
+ * @since 4.0.0
48
+ * @var string $post_type The current post type.
49
+ */
50
+ protected $post_type = '';
51
+
52
+ /**
53
+ * @since 4.0.0
54
+ * @var string $taxonomy The current taxonomy.
55
+ */
56
+ protected $taxonomy = '';
57
+
58
+ /**
59
+ * @since 4.0.0
60
+ * @var bool $doing_ajax Whether we're satisfying an AJAX request.
61
+ */
62
+ protected $doing_ajax = false;
63
+
64
+ /**
65
+ * Constructor, loads actions.
66
+ *
67
+ * @since 4.0.0
68
+ * @access private
69
+ */
70
+ public function __construct() {
71
+
72
+ //* Initialize columns.
73
+ \add_action( 'current_screen', [ $this, '_prepare_columns' ] );
74
+
75
+ //* Ajax handlers for columns.
76
+ \add_action( 'wp_ajax_add-tag', [ $this, '_prepare_columns_wp_ajax_add_tag' ], -1 );
77
+ \add_action( 'wp_ajax_inline-save', [ $this, '_prepare_columns_wp_ajax_inline_save' ], -1 );
78
+ \add_action( 'wp_ajax_inline-save-tax', [ $this, '_prepare_columns_wp_ajax_inline_save_tax' ], -1 );
79
+ }
80
+
81
+ /**
82
+ * Initializes columns for current screen.
83
+ *
84
+ * @since 4.0.0
85
+ * @access private
86
+ *
87
+ * @param \WP_Screen|string $screen \WP_Screen
88
+ */
89
+ public function _prepare_columns( $screen ) {
90
+ $this->init_columns( $screen );
91
+ }
92
+
93
+ /**
94
+ * Initializes columns for adding a tag or category.
95
+ *
96
+ * @since 4.0.0
97
+ * @access private
98
+ */
99
+ public function _prepare_columns_wp_ajax_add_tag() {
100
+
101
+ if ( ! \check_ajax_referer( 'add-tag', '_wpnonce_add-tag', false )
102
+ || empty( $_POST['taxonomy'] ) )
103
+ return;
104
+
105
+ $taxonomy = stripslashes( $_POST['taxonomy'] );
106
+ $tax_object = $taxonomy ? \get_taxonomy( $taxonomy ) : false;
107
+
108
+ if ( $tax_object && \current_user_can( $tax_object->cap->edit_terms ) )
109
+ $this->init_columns_ajax();
110
+ }
111
+
112
+ /**
113
+ * Initializes columns for adding a tag or category.
114
+ *
115
+ * @since 4.0.0
116
+ * @access private
117
+ */
118
+ public function _prepare_columns_wp_ajax_inline_save() {
119
+
120
+ if ( ! \check_ajax_referer( 'inlineeditnonce', '_inline_edit', false )
121
+ || empty( $_POST['post_ID'] )
122
+ || empty( $_POST['post_type'] ) )
123
+ return;
124
+
125
+ $post_type = stripslashes( $_POST['post_type'] );
126
+ $pto = $post_type ? \get_post_type_object( $post_type ) : false;
127
+
128
+ if ( $pto && \current_user_can( 'edit_' . $pto->capability_type, (int) $_POST['post_ID'] ) )
129
+ $this->init_columns_ajax();
130
+ }
131
+
132
+ /**
133
+ * Initializes columns for adding a tag or category.
134
+ *
135
+ * @since 4.0.0
136
+ * @securitycheck 3.0.0 OK.
137
+ * @access private
138
+ */
139
+ public function _prepare_columns_wp_ajax_inline_save_tax() {
140
+
141
+ if ( ! \check_ajax_referer( 'taxinlineeditnonce', '_inline_edit', false )
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
+
151
+ /**
152
+ * Initializes columns.
153
+ *
154
+ * @since 4.0.0
155
+ *
156
+ * @param \WP_Screen $screen The current screen.
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
+
175
+ $this->post_type = $post_type;
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.
185
+ */
186
+ \add_action( 'manage_posts_custom_column', [ $this, '_output_column_contents_for_post' ], 1, 2 );
187
+ \add_action( 'manage_pages_custom_column', [ $this, '_output_column_contents_for_post' ], 1, 2 );
188
+ }
189
+
190
+ /**
191
+ * Initializes columns for AJAX.
192
+ *
193
+ * @since 4.0.0
194
+ * @see callers for CSRF protection.
195
+ * `_prepare_columns_wp_ajax_add_tag()`
196
+ * `_prepare_columns_wp_ajax_inline_save()`
197
+ * `_prepare_columns_wp_ajax_inline_save_tax()`
198
+ */
199
+ private function init_columns_ajax() {
200
+ // phpcs:disable, WordPress.Security.NonceVerification -- _prepare_columns_wp_ajax_* verifies this.
201
+
202
+ $taxonomy = isset( $_POST['taxonomy'] ) ? stripslashes( $_POST['taxonomy'] ) : '';
203
+ $post_type = isset( $_POST['post_type'] ) ? stripslashes( $_POST['post_type'] ) : '';
204
+
205
+ //? /wp-admin/js/inline-edit-tax.js doesn't send post_type, instead, it sends tax_type, which is the same.
206
+ $post_type = $post_type
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
+
217
+ $this->doing_ajax = true;
218
+ $this->post_type = $post_type;
219
+ $this->taxonomy = $taxonomy;
220
+
221
+ $screen_id = isset( $_POST['screen'] ) ? stripslashes( $_POST['screen'] ) : '';
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.
233
+ * Many CPT plugins rely on these.
234
+ */
235
+ \add_action( 'manage_posts_custom_column', [ $this, '_output_column_contents_for_post' ], 1, 2 );
236
+ \add_action( 'manage_pages_custom_column', [ $this, '_output_column_contents_for_post' ], 1, 2 );
237
+ } elseif ( $taxonomy ) {
238
+ /**
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
+ }
248
+
249
+ /**
250
+ * Add column on edit(-tags).php
251
+ *
252
+ * @since 4.0.0
253
+ * @access private
254
+ * @abstract
255
+ *
256
+ * @param array $columns The existing columns.
257
+ * @return array $columns The adjusted columns.
258
+ */
259
+ abstract public function _add_column( $columns );
260
+
261
+ /**
262
+ * Outputs the SEO Bar for posts and pages.
263
+ *
264
+ * @since 4.0.0
265
+ * @access private
266
+ * @abstract
267
+ *
268
+ * @param string $column_name The name of the column to display.
269
+ * @param int $post_id The current post ID.
270
+ */
271
+ abstract public function _output_column_contents_for_post( $column_name, $post_id );
272
+
273
+ /**
274
+ * Returns the contents for a column on tax screens.
275
+ *
276
+ * @since 4.0.0
277
+ * @access private
278
+ * @abstract
279
+ * @NOTE Unlike _output_seo_bar_for_column(), this is a filter callback.
280
+ * Because of this, the first parameter is a useless string, which must be extended.
281
+ * Discrepancy: https://core.trac.wordpress.org/ticket/33521
282
+ * With this, the proper function name should be "_get..." or "_add...", but not "_output.."
283
+ *
284
+ * @param string $string Blank string.
285
+ * @param string $column_name Name of the column.
286
+ * @param string $term_id Term ID.
287
+ * @return string The column contents.
288
+ */
289
+ abstract public function _output_column_contents_for_term( $string, $column_name, $term_id );
290
+ }
inc/classes/bridges/ping.class.php ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Bridges\Ping
4
+ * @subpackage The_SEO_Framework\Sitemap
5
+ */
6
+
7
+ namespace The_SEO_Framework\Bridges;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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
+ * Pings search engines.
30
+ *
31
+ * @since 4.0.0
32
+ * @uses \The_SEO_Framework\Bridges\Sitemap.
33
+ * @access protected
34
+ * @final Can't be extended.
35
+ */
36
+ final class Ping {
37
+ use \The_SEO_Framework\Traits\Enclose_Stray_Private;
38
+
39
+ /**
40
+ * The constructor, can't be initialized.
41
+ */
42
+ private function __construct() { }
43
+
44
+ /**
45
+ * Prepares a CRON-based ping within 30 seconds of calling this.
46
+ *
47
+ * @since 4.0.0
48
+ */
49
+ public static function engage_pinging_cron() {
50
+ \wp_schedule_single_event( time() + 30, 'tsf_sitemap_cron_hook' );
51
+ }
52
+
53
+ /**
54
+ * Pings search engines.
55
+ *
56
+ * @since 2.2.9
57
+ * @since 2.8.0 Only worked when the blog was not public...
58
+ * @since 3.1.0 Now allows one ping per language.
59
+ * @uses $this->add_cache_key_suffix()
60
+ * @since 3.2.3 1. Now works as intended again.
61
+ * 2. Removed Easter egg.
62
+ * @since 4.0.0 Moved to \The_SEO_Framework\Bridges\Ping
63
+ * @since 4.0.2 Added action.
64
+ *
65
+ * @return void Early if blog is not public.
66
+ */
67
+ public static function ping_search_engines() {
68
+
69
+ $tsf = \the_seo_framework();
70
+
71
+ if ( $tsf->get_option( 'site_noindex' ) || ! $tsf->is_blog_public() ) return;
72
+
73
+ $transient = $tsf->generate_cache_key( 0, '', 'ping' );
74
+
75
+ //* NOTE: Use legacy get_transient to bypass TSF's transient filters and prevent ping spam.
76
+ if ( false === \get_transient( $transient ) ) {
77
+ if ( $tsf->get_option( 'ping_google' ) )
78
+ static::ping_google();
79
+
80
+ if ( $tsf->get_option( 'ping_bing' ) )
81
+ static::ping_bing();
82
+
83
+ /**
84
+ * @since 4.0.2
85
+ * @param string $class The current class name.
86
+ */
87
+ \do_action( 'the_seo_framework_ping_search_engines', static::class );
88
+
89
+ /**
90
+ * @since 2.5.1
91
+ * @param int $expiration The minimum time between two pings.
92
+ */
93
+ $expiration = (int) \apply_filters( 'the_seo_framework_sitemap_throttle_s', HOUR_IN_SECONDS );
94
+
95
+ //* @NOTE: Using legacy set_transient to bypass TSF's transient filters and prevent ping spam.
96
+ \set_transient( $transient, 1, $expiration );
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Pings the main sitemap location to Google.
102
+ *
103
+ * @since 2.2.9
104
+ * @since 3.1.0 Updated ping URL. Old one still worked, too.
105
+ * @since 4.0.0 Moved to \The_SEO_Framework\Bridges\Ping
106
+ * @link https://support.google.com/webmasters/answer/6065812?hl=en
107
+ */
108
+ public static function ping_google() {
109
+ $pingurl = 'http://www.google.com/ping?sitemap=' . rawurlencode(
110
+ \The_SEO_Framework\Bridges\Sitemap::get_instance()->get_expected_sitemap_endpoint_url()
111
+ );
112
+ \wp_safe_remote_get( $pingurl, [ 'timeout' => 3 ] );
113
+ }
114
+
115
+ /**
116
+ * Pings the main sitemap location to Bing.
117
+ *
118
+ * @since 2.2.9
119
+ * @since 3.2.3 Updated ping URL. Old one still worked, too.
120
+ * @since 4.0.0 Moved to \The_SEO_Framework\Bridges\Ping
121
+ * @link https://www.bing.com/webmaster/help/how-to-submit-sitemaps-82a15bd4
122
+ */
123
+ public static function ping_bing() {
124
+ $pingurl = 'http://www.bing.com/ping?sitemap=' . rawurlencode(
125
+ \The_SEO_Framework\Bridges\Sitemap::get_instance()->get_expected_sitemap_endpoint_url()
126
+ );
127
+ \wp_safe_remote_get( $pingurl, [ 'timeout' => 3 ] );
128
+ }
129
+ }
inc/classes/bridges/postsettings.class.php ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Bridges\PostSettings
4
+ * @subpackage The_SEO_Framework\Admin\Edit\Post
5
+ */
6
+
7
+ namespace The_SEO_Framework\Bridges;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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
+ * Prepares the Post Settings meta box interface.
30
+ *
31
+ * TODO carry over what we implemented in TSFEM, and make that the standard.
32
+ *
33
+ * @since 4.0.0
34
+ * @access protected
35
+ * @internal
36
+ * @final Can't be extended.
37
+ */
38
+ final class PostSettings {
39
+ use \The_SEO_Framework\Traits\Enclose_Stray_Private;
40
+
41
+ /**
42
+ * Registers the meta box for the Post edit screens.
43
+ *
44
+ * @since 4.0.0
45
+ *
46
+ * @param string $post_type The current Post Type.
47
+ */
48
+ public static function _prepare_meta_box( $post_type ) {
49
+
50
+ $tsf = \the_seo_framework();
51
+
52
+ $label = $tsf->get_post_type_label( $post_type );
53
+
54
+ /**
55
+ * @since 2.9.0
56
+ * @param string $context Accepts 'normal', 'side', and 'advanced'.
57
+ */
58
+ $context = (string) \apply_filters( 'the_seo_framework_metabox_context', 'normal' );
59
+
60
+ /**
61
+ * @since 2.6.0
62
+ * @param string $default Accepts 'high', 'default', 'low'
63
+ * Defaults to high, this box is seen right below the post/page edit screen.
64
+ */
65
+ $priority = (string) \apply_filters( 'the_seo_framework_metabox_priority', 'high' );
66
+
67
+ if ( $tsf->is_front_page_by_id( $tsf->get_the_real_ID() ) ) {
68
+ if ( $tsf->can_access_settings() ) {
69
+ $schema = \is_rtl() ? '%2$s - %1$s' : '%1$s - %2$s';
70
+ $title = sprintf(
71
+ $schema,
72
+ \esc_html__( 'Homepage SEO Settings', 'autodescription' ),
73
+ $tsf->make_info(
74
+ \__( 'The SEO Settings may take precedence over these settings.', 'autodescription' ),
75
+ $tsf->seo_settings_page_url(),
76
+ false
77
+ )
78
+ );
79
+ } else {
80
+ $title = \esc_html__( 'Homepage SEO Settings', 'autodescription' );
81
+ }
82
+ } else {
83
+ /* translators: %s = Post Type label */
84
+ $title = sprintf( \esc_html__( '%s SEO Settings', 'autodescription' ), $label );
85
+ }
86
+
87
+ \add_meta_box( 'tsf-inpost-box', $title, __CLASS__ . '::_meta_box', $post_type, $context, $priority, [] );
88
+ }
89
+
90
+ /**
91
+ * Setting nav tab wrappers.
92
+ * Outputs Tabs and settings content.
93
+ *
94
+ * @since 4.0.0
95
+ * @access private
96
+ *
97
+ * @param string $id The nav-tab ID.
98
+ * @param array $tabs The tab content {
99
+ * string tab ID => array : {
100
+ * string name : Tab name.
101
+ * callable callback : Output function.
102
+ * string dashicon : The dashicon to use.
103
+ * mixed args : Optional callback function args.
104
+ * }
105
+ * }
106
+ * @param bool $use_tabs Whether to output tabs, only works when $tabs count is greater than 1.
107
+ */
108
+ public static function _flex_nav_tab_wrapper( $id, $tabs = [], $use_tabs = true ) {
109
+ \the_seo_framework()->get_view( 'edit/wrap-nav', get_defined_vars() );
110
+ \the_seo_framework()->get_view( 'edit/wrap-content', get_defined_vars() );
111
+ }
112
+
113
+ /**
114
+ * Outputs the meta box.
115
+ *
116
+ * @since 4.0.0
117
+ */
118
+ public static function _meta_box() {
119
+
120
+ static::output_nonce_field();
121
+
122
+ $tsf = \the_seo_framework();
123
+
124
+ /**
125
+ * @since 2.9.0
126
+ */
127
+ \do_action( 'the_seo_framework_pre_page_inpost_box' );
128
+
129
+ $tsf->is_gutenberg_page()
130
+ and $tsf->get_view( 'edit/seo-settings-singular-gutenberg-data' );
131
+ $tsf->get_view( 'edit/seo-settings-singular' );
132
+
133
+ /**
134
+ * @since 2.9.0
135
+ */
136
+ \do_action( 'the_seo_framework_pro_page_inpost_box' );
137
+ }
138
+
139
+ /**
140
+ * Outputs nonce fields for the post settings.
141
+ * Redundant, but added for sanity.
142
+ *
143
+ * @since 4.0.0
144
+ */
145
+ private static function output_nonce_field() {
146
+ $tsf = \the_seo_framework();
147
+ \wp_nonce_field( $tsf->inpost_nonce_field, $tsf->inpost_nonce_name );
148
+ }
149
+
150
+ /**
151
+ * Outputs the Post SEO box general tab.
152
+ *
153
+ * @since 4.0.0
154
+ */
155
+ public static function _general_tab() {
156
+ /**
157
+ * @since 2.9.0
158
+ */
159
+ \do_action( 'the_seo_framework_pre_page_inpost_general_tab' );
160
+ \the_seo_framework()->get_view( 'edit/seo-settings-singular', [], 'general' );
161
+ /**
162
+ * @since 2.9.0
163
+ */
164
+ \do_action( 'the_seo_framework_pro_page_inpost_general_tab' );
165
+ }
166
+
167
+ /**
168
+ * Outputs the Post SEO box visibility tab.
169
+ *
170
+ * @since 4.0.0
171
+ */
172
+ public static function _visibility_tab() {
173
+ /**
174
+ * @since 2.9.0
175
+ */
176
+ \do_action( 'the_seo_framework_pre_page_inpost_visibility_tab' );
177
+ \the_seo_framework()->get_view( 'edit/seo-settings-singular', [], 'visibility' );
178
+ /**
179
+ * @since 2.9.0
180
+ */
181
+ \do_action( 'the_seo_framework_pro_page_inpost_visibility_tab' );
182
+ }
183
+
184
+ /**
185
+ * Outputs the Post SEO box social tab.
186
+ *
187
+ * @since 4.0.0
188
+ */
189
+ public static function _social_tab() {
190
+ /**
191
+ * @since 2.9.0
192
+ */
193
+ \do_action( 'the_seo_framework_pre_page_inpost_social_tab' );
194
+ \the_seo_framework()->get_view( 'edit/seo-settings-singular', [], 'social' );
195
+ /**
196
+ * @since 2.9.0
197
+ */
198
+ \do_action( 'the_seo_framework_pro_page_inpost_social_tab' );
199
+ }
200
+ }
inc/classes/bridges/scripts.class.php ADDED
@@ -0,0 +1,1015 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Bridges\Scripts
4
+ * @subpackage The_SEO_Framework\Scripts
5
+ */
6
+
7
+ namespace The_SEO_Framework\Bridges;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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
+ * 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
+ new Scripts();
37
+ };
38
+
39
+ /**
40
+ * Prepares admin GUI scripts. Auto-invokes everything the moment this file is required.
41
+ * Relies on \The_SEO_Framework\Builders\Scripts to register and load scripts.
42
+ *
43
+ * What's a state, and what's a param?
44
+ * - states may and are expected to be changed, like a page title.
45
+ * - params shouldn't change, like the page ID.
46
+ *
47
+ * @since 4.0.0
48
+ * @see \The_SEO_Framework\Builders\Scripts
49
+ * @access protected
50
+ * Use static calls The_SEO_Framework\Bridges\Scripts::funcname()
51
+ * @final Can't be extended.
52
+ */
53
+ final class Scripts {
54
+ use \The_SEO_Framework\Traits\Enclose_Stray_Private;
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
+ *
65
+ * Use this if the actions need to be registered early, but nothing else of
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
+ *
75
+ * This probably autoloads at action "admin_enqueue_scripts", priority "0".
76
+ *
77
+ * @since 4.0.0
78
+ * @access private
79
+ * @staticvar int $count Enforces singleton.
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.
92
+ *
93
+ * @since 4.0.0
94
+ * @access private
95
+ * @internal This always runs; build your own loader from the public methods, instead.
96
+ */
97
+ public static function _init() {
98
+
99
+ $tsf = \the_seo_framework();
100
+
101
+ $_scripts = [
102
+ static::get_tsf_scripts(),
103
+ static::get_tt_scripts(),
104
+ ];
105
+
106
+ if ( $tsf->is_post_edit() ) {
107
+ static::prepare_media_scripts();
108
+
109
+ $_scripts[] = static::get_post_edit_scripts();
110
+ $_scripts[] = static::get_media_scripts();
111
+ $_scripts[] = static::get_title_scripts();
112
+ $_scripts[] = static::get_description_scripts();
113
+ $_scripts[] = static::get_social_scripts();
114
+ $_scripts[] = static::get_primaryterm_scripts();
115
+ $_scripts[] = static::get_ays_scripts();
116
+
117
+ if ( $tsf->get_option( 'display_pixel_counter' ) || $tsf->get_option( 'display_character_counter' ) )
118
+ $_scripts[] = static::get_counter_scripts();
119
+
120
+ if ( $tsf->is_gutenberg_page() )
121
+ $_scripts[] = static::get_gutenberg_compat_scripts();
122
+ } elseif ( $tsf->is_term_edit() ) {
123
+ static::prepare_media_scripts();
124
+
125
+ $_scripts[] = static::get_term_edit_scripts();
126
+ $_scripts[] = static::get_media_scripts();
127
+ $_scripts[] = static::get_title_scripts();
128
+ $_scripts[] = static::get_description_scripts();
129
+ $_scripts[] = static::get_social_scripts();
130
+ $_scripts[] = static::get_ays_scripts();
131
+
132
+ if ( $tsf->get_option( 'display_pixel_counter' ) || $tsf->get_option( 'display_character_counter' ) )
133
+ $_scripts[] = static::get_counter_scripts();
134
+ } elseif ( $tsf->is_wp_lists_edit() ) {
135
+ $_scripts[] = static::get_list_edit_scripts();
136
+ } elseif ( $tsf->is_seo_settings_page() ) {
137
+ static::prepare_media_scripts();
138
+ static::prepare_metabox_scripts();
139
+
140
+ $_scripts[] = static::get_seo_settings_scripts();
141
+ $_scripts[] = static::get_media_scripts();
142
+ $_scripts[] = static::get_title_scripts();
143
+ $_scripts[] = static::get_description_scripts();
144
+ $_scripts[] = static::get_social_scripts();
145
+ $_scripts[] = static::get_ays_scripts();
146
+
147
+ // Always load unconditionally, options may enable the counters dynamically.
148
+ $_scripts[] = static::get_counter_scripts();
149
+ }
150
+
151
+ /**
152
+ * @since 3.1.0
153
+ * @since 4.0.0 1. Now holds all scripts.
154
+ * 2. Added $loader parameter.
155
+ * @param array $scripts The default CSS and JS loader settings.
156
+ * @param string $builder The \The_SEO_Framework\Builders\Scripts builder class name.
157
+ * @param string $loader The \The_SEO_Framework\Bridges\Scripts loader class name.
158
+ */
159
+ $_scripts = \apply_filters_ref_array(
160
+ 'the_seo_framework_scripts',
161
+ [
162
+ $_scripts,
163
+ \The_SEO_Framework\Builders\Scripts::class,
164
+ static::class, // i.e. `\The_SEO_Framework\Bridges\Scripts::class`
165
+ ]
166
+ );
167
+
168
+ \The_SEO_Framework\Builders\Scripts::register( $_scripts );
169
+ }
170
+
171
+ /**
172
+ * Decodes entities of a string.
173
+ *
174
+ * @since 4.0.0
175
+ *
176
+ * @param mixed $value If string, it'll be decoded.
177
+ * @return mixed
178
+ */
179
+ public static function decode_entities( $value ) {
180
+ return $value && is_string( $value ) ? html_entity_decode( $value, ENT_QUOTES | ENT_COMPAT, 'UTF-8' ) : $value;
181
+ }
182
+
183
+ /**
184
+ * Decodes all entities of the input.
185
+ *
186
+ * @since 4.0.0
187
+ * @uses static::decode_entities();
188
+ *
189
+ * @param mixed $values The entries to decode.
190
+ * @return mixed
191
+ */
192
+ public static function decode_all_entities( $values ) {
193
+
194
+ if ( is_scalar( $values ) )
195
+ return static::decode_entities( $values );
196
+
197
+ foreach ( $values as &$v ) {
198
+ $v = static::decode_entities( $v );
199
+ }
200
+
201
+ return $values;
202
+ }
203
+
204
+ /**
205
+ * Prepares WordPress Media scripts.
206
+ *
207
+ * @since 4.0.0
208
+ */
209
+ public static function prepare_media_scripts() {
210
+
211
+ $tsf = \the_seo_framework();
212
+ $args = [];
213
+
214
+ if ( $tsf->is_post_edit() ) {
215
+ $args['post'] = $tsf->get_the_real_admin_ID();
216
+ }
217
+
218
+ \wp_enqueue_media( $args );
219
+ }
220
+
221
+ /**
222
+ * Prepares WordPress metabox scripts.
223
+ *
224
+ * @since 4.0.0
225
+ */
226
+ public static function prepare_metabox_scripts() {
227
+ \wp_enqueue_script( 'common' );
228
+ \wp_enqueue_script( 'wp-lists' );
229
+ \wp_enqueue_script( 'postbox' );
230
+ }
231
+
232
+ /**
233
+ * Returns the default TSF scripts.
234
+ *
235
+ * @since 4.0.0
236
+ *
237
+ * @return array The script params.
238
+ */
239
+ public static function get_tsf_scripts() {
240
+ return [
241
+ [
242
+ 'id' => 'tsf',
243
+ 'type' => 'css',
244
+ 'deps' => [ 'tsf-tt' ],
245
+ 'autoload' => true,
246
+ 'hasrtl' => false,
247
+ 'name' => 'tsf',
248
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
249
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
250
+ ],
251
+ [
252
+ 'id' => 'tsf',
253
+ 'type' => 'js',
254
+ 'deps' => [ 'jquery', 'tsf-tt' ],
255
+ 'autoload' => true,
256
+ 'name' => 'tsf',
257
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
258
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
259
+ 'l10n' => [
260
+ 'name' => 'tsfL10n',
261
+ 'data' => [
262
+ 'nonces' => [
263
+ /**
264
+ * Use $tsf->get_settings_capability() ?... might conflict with other nonces.
265
+ */
266
+ 'manage_options' => \current_user_can( 'manage_options' ) ? \wp_create_nonce( 'tsf-ajax-manage_options' ) : false,
267
+ 'upload_files' => \current_user_can( 'upload_files' ) ? \wp_create_nonce( 'tsf-ajax-upload_files' ) : false,
268
+ 'edit_posts' => \current_user_can( 'edit_posts' ) ? \wp_create_nonce( 'tsf-ajax-edit_posts' ) : false,
269
+ ],
270
+ 'states' => [
271
+ 'isRTL' => (bool) \is_rtl(),
272
+ 'debug' => \the_seo_framework()->script_debug,
273
+ ],
274
+ ],
275
+ ],
276
+ ],
277
+ ];
278
+ }
279
+
280
+ /**
281
+ * Returns TT (tooltip) scripts params.
282
+ *
283
+ * @since 4.0.0
284
+ *
285
+ * @return array The script params.
286
+ */
287
+ public static function get_tt_scripts() {
288
+ return [
289
+ [
290
+ 'id' => 'tsf-tt',
291
+ 'type' => 'css',
292
+ 'deps' => [],
293
+ 'autoload' => true,
294
+ 'hasrtl' => false,
295
+ 'name' => 'tt',
296
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
297
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
298
+ 'inline' => [
299
+ '.tsf-tooltip-text-wrap' => [
300
+ 'background-color:{{$bg_accent}}',
301
+ 'color:{{$rel_bg_accent}}',
302
+ ],
303
+ '.tsf-tooltip-arrow:after' => [
304
+ 'border-top-color:{{$bg_accent}}',
305
+ ],
306
+ '.tsf-tooltip-down .tsf-tooltip-arrow:after' => [
307
+ 'border-bottom-color:{{$bg_accent}}',
308
+ ],
309
+ '.tsf-tooltip-text' => [
310
+ \is_rtl() ? 'direction:rtl' : '',
311
+ ],
312
+ ],
313
+ ],
314
+ [
315
+ 'id' => 'tsf-tt',
316
+ 'type' => 'js',
317
+ 'deps' => [ 'jquery' ],
318
+ 'autoload' => true,
319
+ 'name' => 'tt',
320
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
321
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
322
+ ],
323
+ ];
324
+ }
325
+
326
+ /**
327
+ * Returns AYS (Are you sure?) scripts params.
328
+ *
329
+ * @since 4.0.0
330
+ *
331
+ * @return array The script params.
332
+ */
333
+ public static function get_ays_scripts() {
334
+ return [
335
+ [
336
+ 'id' => 'tsf-ays',
337
+ 'type' => 'js',
338
+ 'deps' => [ 'jquery' ],
339
+ 'autoload' => true,
340
+ 'name' => 'ays',
341
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
342
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
343
+ 'l10n' => [
344
+ 'name' => 'tsfAysL10n',
345
+ 'data' => [
346
+ 'i18n' => [
347
+ 'saveAlert' => \__( 'The changes you made will be lost if you navigate away from this page.', 'autodescription' ),
348
+ ],
349
+ ],
350
+ ],
351
+ ],
352
+ ];
353
+ }
354
+
355
+ /**
356
+ * Returns LE (List Edit) scripts params.
357
+ *
358
+ * @since 4.0.0
359
+ *
360
+ * @return array The script params.
361
+ */
362
+ public static function get_list_edit_scripts() {
363
+ return [
364
+ [
365
+ 'id' => 'tsf-le',
366
+ 'type' => 'css',
367
+ 'deps' => [ 'tsf' ],
368
+ 'autoload' => true,
369
+ 'hasrtl' => false,
370
+ 'name' => 'le',
371
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
372
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
373
+ ],
374
+ [
375
+ 'id' => 'tsf-le',
376
+ 'type' => 'js',
377
+ 'deps' => [ 'jquery', 'tsf' ],
378
+ 'autoload' => true,
379
+ 'name' => 'le',
380
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
381
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
382
+ 'l10n' => [
383
+ 'name' => 'tsfLeL10n',
384
+ 'data' => [],
385
+ ],
386
+ ],
387
+ ];
388
+ }
389
+
390
+ /**
391
+ * Returns the SEO Settings page script params.
392
+ *
393
+ * @since 4.0.0
394
+ *
395
+ * @return array The script params.
396
+ */
397
+ public static function get_seo_settings_scripts() {
398
+
399
+ $tsf = \the_seo_framework();
400
+
401
+ $front_id = $tsf->get_the_front_page_ID();
402
+
403
+ return [
404
+ [
405
+ 'id' => 'tsf-settings',
406
+ 'type' => 'js',
407
+ 'deps' => [ 'jquery', 'tsf-ays', 'tsf-title', 'tsf-description', 'tsf', 'tsf-tt', 'wp-color-picker' ],
408
+ 'autoload' => true,
409
+ 'name' => 'settings',
410
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
411
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
412
+ 'l10n' => [
413
+ 'name' => 'tsfSettingsL10n',
414
+ 'data' => [
415
+ 'i18n' => [
416
+ 'confirmReset' => \__( 'Are you sure you want to reset all SEO settings to their defaults?', 'autodescription' ),
417
+ // phpcs:ignore, WordPress.WP.I18n -- WordPress doesn't have a comment, either.
418
+ 'privateTitle' => static::decode_entities( trim( str_replace( '%s', '', \__( 'Private: %s', 'default' ) ) ) ),
419
+ // phpcs:ignore, WordPress.WP.I18n -- WordPress doesn't have a comment, either.
420
+ 'protectedTitle' => static::decode_entities( trim( str_replace( '%s', '', \__( 'Protected: %s', 'default' ) ) ) ),
421
+ ],
422
+ 'states' => [
423
+ 'isFrontPrivate' => $front_id && $tsf->is_private( $front_id ),
424
+ 'isFrontProtected' => $front_id && $tsf->is_password_protected( $front_id ),
425
+ ],
426
+ ],
427
+ ],
428
+ ],
429
+ [
430
+ 'id' => 'tsf-settings',
431
+ 'type' => 'css',
432
+ 'deps' => [ 'tsf', 'tsf-tt', 'wp-color-picker' ],
433
+ 'autoload' => true,
434
+ 'hasrtl' => false,
435
+ 'name' => 'settings',
436
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
437
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
438
+ ],
439
+ ];
440
+ }
441
+
442
+ /**
443
+ * Returns Post edit scripts params.
444
+ *
445
+ * @since 4.0.0
446
+ *
447
+ * @return array The script params.
448
+ */
449
+ public static function get_post_edit_scripts() {
450
+
451
+ $tsf = \the_seo_framework();
452
+ $id = $tsf->get_the_real_ID();
453
+
454
+ $is_static_frontpage = $tsf->is_static_frontpage( $id );
455
+
456
+ if ( $is_static_frontpage ) {
457
+ $additions_forced_disabled = ! $tsf->get_option( 'homepage_tagline' );
458
+ $additions_forced_enabled = ! $additions_forced_disabled;
459
+ } else {
460
+ $additions_forced_disabled = (bool) $tsf->get_option( 'title_rem_additions' );
461
+ $additions_forced_enabled = false;
462
+ }
463
+
464
+ return [
465
+ [
466
+ 'id' => 'tsf-post',
467
+ 'type' => 'js',
468
+ 'deps' => [ 'jquery', 'tsf-ays', 'tsf-title', 'tsf-description', 'tsf-social', 'tsf-tt', 'tsf' ],
469
+ 'autoload' => true,
470
+ 'name' => 'post',
471
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
472
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
473
+ 'l10n' => [
474
+ 'name' => 'tsfPostL10n',
475
+ 'data' => [
476
+ 'states' => [
477
+ 'isPrivate' => $tsf->is_private( $id ),
478
+ 'isProtected' => $tsf->is_password_protected( $id ),
479
+ 'isGutenbergPage' => $tsf->is_gutenberg_page(),
480
+ 'id' => (int) $id,
481
+ ],
482
+ 'i18n' => [
483
+ // phpcs:ignore, WordPress.WP.I18n -- WordPress doesn't have a comment, either.
484
+ 'privateTitle' => static::decode_entities( trim( str_replace( '%s', '', \__( 'Private: %s', 'default' ) ) ) ),
485
+ // phpcs:ignore, WordPress.WP.I18n -- WordPress doesn't have a comment, either.
486
+ 'protectedTitle' => static::decode_entities( trim( str_replace( '%s', '', \__( 'Protected: %s', 'default' ) ) ) ),
487
+ ],
488
+ 'params' => [
489
+ 'isFront' => $is_static_frontpage,
490
+ 'refTitleLocked' => $is_static_frontpage && $tsf->get_option( 'homepage_title' ),
491
+ 'refDescriptionLocked' => $is_static_frontpage && $tsf->get_option( 'homepage_description' ),
492
+ 'stripTitleTags' => (bool) $tsf->get_option( 'title_strip_tags' ),
493
+ 'additionsForcedDisabled' => $additions_forced_disabled,
494
+ 'additionsForcedEnabled' => $additions_forced_enabled,
495
+ ],
496
+ ],
497
+ ],
498
+ ],
499
+ [
500
+ 'id' => 'tsf-post',
501
+ 'type' => 'css',
502
+ 'deps' => [ 'tsf-tt', 'tsf' ],
503
+ 'autoload' => true,
504
+ 'hasrtl' => false,
505
+ 'name' => 'post',
506
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
507
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
508
+ 'inline' => [
509
+ '.tsf-flex-nav-tab .tsf-flex-nav-tab-radio:checked + .tsf-flex-nav-tab-label' => [
510
+ 'box-shadow:0 -2px 0 0 {{$color_accent}} inset, 0 0 0 0 {{$color_accent}} inset',
511
+ ],
512
+ '.tsf-flex-nav-tab .tsf-flex-nav-tab-radio:focus + .tsf-flex-nav-tab-label:not(.tsf-no-focus-ring)' => [
513
+ 'box-shadow:0 -2px 0 0 {{$color_accent}} inset, 0 0 0 1px {{$color_accent}} inset',
514
+ ],
515
+ ],
516
+ ],
517
+ ];
518
+ }
519
+
520
+ /**
521
+ * Returns Term scripts params.
522
+ *
523
+ * @since 4.0.0
524
+ *
525
+ * @return array The script params.
526
+ */
527
+ public static function get_term_edit_scripts() {
528
+
529
+ $tsf = \the_seo_framework();
530
+
531
+ $taxonomy = $tsf->get_current_taxonomy();
532
+
533
+ $additions_forced_disabled = (bool) $tsf->get_option( 'title_rem_additions' );
534
+ $prefixes_forced_disabled = (bool) $tsf->get_option( 'title_rem_prefixes' );
535
+
536
+ $_prefix = $tsf->get_tax_type_label( $taxonomy );
537
+ /* translators: Taxonomy term archive title. 1: Taxonomy singular name, 2: Current taxonomy term */
538
+ $term_prefix = sprintf( \__( '%1$s: %2$s', 'autodescription' ), $_prefix, '' );
539
+
540
+ return [
541
+ [
542
+ 'id' => 'tsf-term',
543
+ 'type' => 'js',
544
+ 'deps' => [ 'jquery', 'tsf-ays', 'tsf-title', 'tsf-description', 'tsf-social', 'tsf-tt', 'tsf' ],
545
+ 'autoload' => true,
546
+ 'name' => 'term',
547
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
548
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
549
+ 'l10n' => [
550
+ 'name' => 'tsfTermL10n',
551
+ 'data' => [
552
+ 'params' => [
553
+ 'stripTitleTags' => (bool) $tsf->get_option( 'title_strip_tags' ),
554
+ 'additionsForcedDisabled' => $additions_forced_disabled,
555
+ 'prefixesForcedDisabled' => $prefixes_forced_disabled,
556
+ 'termPrefix' => static::decode_entities( $term_prefix ),
557
+ ],
558
+ ],
559
+ ],
560
+ ],
561
+ [
562
+ 'id' => 'tsf-term',
563
+ 'type' => 'css',
564
+ 'deps' => [ 'tsf-tt', 'tsf' ],
565
+ 'autoload' => true,
566
+ 'hasrtl' => false,
567
+ 'name' => 'term',
568
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
569
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
570
+ ],
571
+ ];
572
+ }
573
+
574
+ /**
575
+ * Returns Gutenberg compatibility scripts params.
576
+ *
577
+ * @since 4.0.0
578
+ *
579
+ * @return array The script params.
580
+ */
581
+ public static function get_gutenberg_compat_scripts() {
582
+ return [
583
+ [
584
+ 'id' => 'tsf-gbc',
585
+ 'type' => 'js',
586
+ 'deps' => [ 'jquery', 'tsf', 'tsf-post', 'wp-editor', 'wp-data', 'lodash', 'react' ],
587
+ 'autoload' => true,
588
+ 'name' => 'gbc',
589
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
590
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
591
+ 'l10n' => [
592
+ 'name' => 'tsfGBCL10n',
593
+ 'data' => [],
594
+ ],
595
+ ],
596
+ ];
597
+ }
598
+
599
+ /**
600
+ * Returns Media scripts params.
601
+ *
602
+ * @since 4.0.0
603
+ *
604
+ * @return array The script params.
605
+ */
606
+ public static function get_media_scripts() {
607
+ return [
608
+ 'id' => 'tsf-media',
609
+ 'type' => 'js',
610
+ 'deps' => [ 'jquery', 'media', 'tsf-tt', 'tsf' ],
611
+ 'autoload' => true,
612
+ 'name' => 'media',
613
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
614
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
615
+ 'l10n' => [
616
+ 'name' => 'tsfMediaL10n',
617
+ 'data' => [
618
+ 'labels' => [
619
+ 'social' => [
620
+ 'imgSelect' => \esc_attr__( 'Select Image', 'autodescription' ),
621
+ 'imgSelectTitle' => \esc_attr_x( 'Select social image', 'Button hover', 'autodescription' ),
622
+ 'imgChange' => \esc_attr__( 'Change Image', 'autodescription' ),
623
+ 'imgRemove' => \esc_attr__( 'Remove Image', 'autodescription' ),
624
+ 'imgRemoveTitle' => \esc_attr__( 'Remove selected social image', 'autodescription' ),
625
+ 'imgFrameTitle' => \esc_attr_x( 'Select Social Image', 'Frame title', 'autodescription' ),
626
+ 'imgFrameButton' => \esc_attr__( 'Use this image', 'autodescription' ),
627
+ ],
628
+ 'logo' => [
629
+ 'imgSelect' => \esc_attr__( 'Select Logo', 'autodescription' ),
630
+ 'imgSelectTitle' => '',
631
+ 'imgChange' => \esc_attr__( 'Change Logo', 'autodescription' ),
632
+ 'imgRemove' => \esc_attr__( 'Remove Logo', 'autodescription' ),
633
+ 'imgRemoveTitle' => \esc_attr__( 'Unset selected logo', 'autodescription' ),
634
+ 'imgFrameTitle' => \esc_attr_x( 'Select Logo', 'Frame title', 'autodescription' ),
635
+ 'imgFrameButton' => \esc_attr__( 'Use this image', 'autodescription' ),
636
+ ],
637
+ ],
638
+ 'nonce' => \current_user_can( 'upload_files' ) ? \wp_create_nonce( 'tsf-ajax-upload_files' ) : false,
639
+ ],
640
+ ],
641
+ ];
642
+ }
643
+
644
+ /**
645
+ * Returns Title scripts params.
646
+ *
647
+ * @since 4.0.0
648
+ *
649
+ * @return array The script params.
650
+ */
651
+ public static function get_title_scripts() {
652
+
653
+ $tsf = \the_seo_framework();
654
+
655
+ $_query = [
656
+ 'id' => $tsf->is_seo_settings_page() ? $tsf->get_the_front_page_ID() : $tsf->get_the_real_ID(),
657
+ 'taxonomy' => $tsf->get_current_taxonomy(),
658
+ ];
659
+
660
+ if ( ! $_query['taxonomy'] && $tsf->is_static_frontpage( $_query['id'] ) ) {
661
+ $addition = $tsf->get_home_page_tagline();
662
+ $seplocation = $tsf->get_home_title_seplocation();
663
+ } else {
664
+ $addition = $tsf->get_blogname();
665
+ $seplocation = $tsf->get_title_seplocation();
666
+ }
667
+
668
+ // NOTE: The custom fields can't be filtered...
669
+ if ( $tsf->is_seo_settings_page() && $tsf->has_page_on_front() ) {
670
+ $_default_title = $tsf->get_post_meta_item( '_genesis_title', $_query['id'] ) ?: '';
671
+ } elseif ( ! $_query['taxonomy'] && $tsf->is_static_frontpage( $_query['id'] ) ) {
672
+ $_default_title = $tsf->get_option( 'homepage_title' ) ?: '';
673
+ } else {
674
+ $_default_title = '';
675
+ }
676
+
677
+ $_default_title = $_default_title ?: $tsf->get_filtered_raw_generated_title( $_query );
678
+
679
+ return [
680
+ 'id' => 'tsf-title',
681
+ 'type' => 'js',
682
+ 'deps' => [ 'jquery', 'tsf' ],
683
+ 'autoload' => true,
684
+ 'name' => 'title',
685
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
686
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
687
+ 'l10n' => [
688
+ 'name' => 'tsfTitleL10n',
689
+ 'data' => [
690
+ 'states' => [
691
+ 'useTagline' => $tsf->use_title_branding( $_query ),
692
+ 'titleSeparator' => static::decode_entities( $tsf->s_title_raw( $tsf->get_title_separator() ) ),
693
+ 'additionPlacement' => 'left' === $seplocation ? 'before' : 'after',
694
+ 'additionValue' => static::decode_entities( $tsf->s_title_raw( $addition ) ),
695
+ 'defaultTitle' => static::decode_entities( $tsf->s_title_raw( $_default_title ) ),
696
+ 'prefixPlacement' => \is_rtl() ? 'after' : 'before',
697
+ ],
698
+ 'params' => [
699
+ 'untitledTitle' => static::decode_entities( $tsf->s_title_raw( $tsf->get_static_untitled_title() ) ),
700
+ ],
701
+ ],
702
+ ],
703
+ ];
704
+ }
705
+
706
+ /**
707
+ * Returns Description scripts params.
708
+ *
709
+ * @since 4.0.0
710
+ *
711
+ * @return array The script params.
712
+ */
713
+ public static function get_description_scripts() {
714
+
715
+ $tsf = \the_seo_framework();
716
+
717
+ $_query = [
718
+ 'id' => $tsf->is_seo_settings_page() ? $tsf->get_the_front_page_ID() : $tsf->get_the_real_ID(),
719
+ 'taxonomy' => $tsf->get_current_taxonomy(),
720
+ ];
721
+
722
+ $_default_description = '';
723
+
724
+ // NOTE: The custom fields can't be filtered...
725
+ if ( $tsf->is_seo_settings_page() && $tsf->has_page_on_front() ) {
726
+ $_default_description = $tsf->get_post_meta_item( '_genesis_description', $_query['id'] ) ?: '';
727
+ } elseif ( ! $_query['taxonomy'] && $tsf->is_static_frontpage( $_query['id'] ) ) {
728
+ $_default_description = $tsf->get_option( 'homepage_description' ) ?: '';
729
+ }
730
+
731
+ $_default_description = $_default_description ?: $tsf->get_generated_description( $_query, false );
732
+
733
+ return [
734
+ 'id' => 'tsf-description',
735
+ 'type' => 'js',
736
+ 'deps' => [ 'jquery', 'tsf' ],
737
+ 'autoload' => true,
738
+ 'name' => 'description',
739
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
740
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
741
+ 'l10n' => [
742
+ 'name' => 'tsfDescriptionL10n',
743
+ 'data' => [
744
+ 'states' => [
745
+ 'defaultDescription' => static::decode_entities( $tsf->s_description( $_default_description ) ),
746
+ ],
747
+ ],
748
+ ],
749
+ ];
750
+ }
751
+
752
+ /**
753
+ * Returns Social scripts params.
754
+ *
755
+ * @since 4.0.0
756
+ *
757
+ * @return array The script params.
758
+ */
759
+ public static function get_social_scripts() {
760
+
761
+ $tsf = \the_seo_framework();
762
+
763
+ $_query = [
764
+ 'id' => $tsf->is_seo_settings_page() ? $tsf->get_the_front_page_ID() : $tsf->get_the_real_ID(),
765
+ 'taxonomy' => $tsf->get_current_taxonomy(),
766
+ ];
767
+
768
+ // These placeholders are required as there are three description lengths: search, og, twitter.
769
+ $settings_placeholders = [
770
+ 'ogDesc' => '',
771
+ 'twDesc' => '',
772
+ ];
773
+
774
+ // These locks are required as we have an extra homepage metabox that can override social settings.
775
+ // PH = placeholder
776
+ $home_locks = array_fill_keys(
777
+ [
778
+ 'ogTitleLock',
779
+ 'ogTitlePHLock',
780
+ 'ogDescriptionLock',
781
+ 'ogDescriptionPHLock',
782
+
783
+ 'twTitleLock',
784
+ 'twTitlePHLock',
785
+ 'twDescriptionLock',
786
+ 'twDescriptionPHLock',
787
+ ],
788
+ false
789
+ );
790
+
791
+ if ( $tsf->has_page_on_front() ) {
792
+ if ( $tsf->is_seo_settings_page() ) {
793
+ $home_locks = [
794
+ 'ogTitlePHLock' => (bool) $tsf->get_post_meta_item( '_open_graph_title', $_query['id'] ),
795
+ 'twTitlePHLock' => (bool) $tsf->get_post_meta_item( '_twitter_title', $_query['id'] ),
796
+ 'twDescriptionPHLock' => (bool) $tsf->get_post_meta_item( '_twitter_description', $_query['id'] ),
797
+ 'ogDescriptionPHLock' => (bool) $tsf->get_post_meta_item( '_open_graph_description', $_query['id'] ),
798
+ ];
799
+
800
+ $settings_placeholders = [
801
+ 'ogDesc' => $tsf->get_post_meta_item( '_genesis_description', $_query['id'] ),
802
+ 'twDesc' => $tsf->get_post_meta_item( '_genesis_description', $_query['id'] ),
803
+ ];
804
+ } elseif ( ! $_query['taxonomy'] && $tsf->is_static_frontpage( $_query['id'] ) ) {
805
+ $home_locks = [
806
+ 'ogTitleLock' => (bool) $tsf->get_option( 'homepage_og_title' ),
807
+ 'ogDescriptionLock' => (bool) $tsf->get_option( 'homepage_og_description' ),
808
+ 'twTitleLock' => (bool) $tsf->get_option( 'homepage_twitter_title' ),
809
+ 'twDescriptionLock' => (bool) $tsf->get_option( 'homepage_twitter_description' ),
810
+ ];
811
+
812
+ $settings_placeholders = [
813
+ 'ogDesc' => $tsf->get_option( 'homepage_description' ),
814
+ 'twDesc' => $tsf->get_option( 'homepage_description' ),
815
+ ];
816
+ }
817
+ }
818
+
819
+ $settings_placeholders['ogDesc'] = $tsf->s_description(
820
+ $settings_placeholders['ogDesc'] ?: $tsf->get_generated_open_graph_description( $_query, false )
821
+ );
822
+
823
+ $settings_placeholders['twDesc'] = $tsf->s_description(
824
+ $settings_placeholders['twDesc'] ?: $tsf->get_generated_twitter_description( $_query, false )
825
+ );
826
+
827
+ return [
828
+ 'id' => 'tsf-social',
829
+ 'type' => 'js',
830
+ 'deps' => [ 'jquery', 'tsf' ],
831
+ 'autoload' => true,
832
+ 'name' => 'social',
833
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
834
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
835
+ 'l10n' => [
836
+ 'name' => 'tsfSocialL10n',
837
+ 'data' => [
838
+ 'params' => [
839
+ 'homeLocks' => $home_locks,
840
+ ],
841
+ 'states' => [
842
+ 'placeholders' => static::decode_all_entities( $settings_placeholders ),
843
+ ],
844
+ ],
845
+ ],
846
+ ];
847
+
848
+ }
849
+
850
+ /**
851
+ * Returns Primary Term Selection scripts params.
852
+ *
853
+ * @since 4.0.0
854
+ *
855
+ * @return array The script params.
856
+ */
857
+ public static function get_primaryterm_scripts() {
858
+
859
+ $tsf = \the_seo_framework();
860
+
861
+ $id = $tsf->get_the_real_admin_ID();
862
+
863
+ $post_type = \get_post_type( $id );
864
+ $_taxonomies = $post_type ? $tsf->get_hierarchical_taxonomies_as( 'objects', $post_type ) : [];
865
+ $taxonomies = [];
866
+
867
+ $gutenberg = $tsf->is_gutenberg_page();
868
+
869
+ foreach ( $_taxonomies as $_t ) {
870
+ $singular_name = $tsf->get_tax_type_label( $_t->name );
871
+
872
+ $primary_term_id = $tsf->get_primary_term_id( $id, $_t->name ) ?: 0;
873
+
874
+ if ( ! $primary_term_id ) {
875
+ /**
876
+ * This is essentially how the filter "post_link_category" gets its
877
+ * primary term. However, this is without trying to support PHP 5.2.
878
+ */
879
+ $terms = \get_the_terms( $id, $_t->name );
880
+ if ( $terms && ! \is_wp_error( $terms ) ) {
881
+ $term_ids = array_column( $terms, 'term_id' );
882
+ sort( $term_ids );
883
+ $primary_term_id = reset( $term_ids );
884
+ }
885
+ }
886
+
887
+ $taxonomies[ $_t->name ] = [
888
+ 'name' => $_t->name,
889
+ 'primary' => $primary_term_id,
890
+ ] + (
891
+ $gutenberg ? [
892
+ 'i18n' => [
893
+ /* translators: %s = term name */
894
+ 'selectPrimary' => sprintf( \esc_html__( 'Select Primary %s', 'autodescription' ), $singular_name ),
895
+ ],
896
+ ] : [
897
+ 'i18n' => [
898
+ /* translators: %s = term name */
899
+ 'makePrimary' => sprintf( \esc_html__( 'Make primary %s', 'autodescription' ), strtolower( $singular_name ) ),
900
+ /* translators: %s = term name */
901
+ 'primary' => sprintf( \esc_html__( 'Primary %s', 'autodescription' ), strtolower( $singular_name ) ),
902
+ 'name' => strtolower( $singular_name ),
903
+ ],
904
+ ]
905
+ );
906
+ }
907
+
908
+ $inline_css = [];
909
+ if ( \is_rtl() ) {
910
+ $inline_css = [
911
+ '.tsf-primary-term-selector' => [
912
+ 'float:left;',
913
+ ],
914
+ '.tsf-primary-term-selector-help-wrap' => [
915
+ 'left:25px;',
916
+ 'right:initial;',
917
+ ],
918
+ ];
919
+ }
920
+
921
+ if ( $gutenberg ) {
922
+ $vars = [
923
+ 'id' => 'tsf-pt-gb',
924
+ 'name' => 'pt-gb',
925
+ ];
926
+ $deps = [ 'jquery', 'tsf', 'tsf-post', 'wp-hooks', 'wp-element', 'wp-components', 'wp-url', 'wp-api-fetch', 'lodash', 'react' ];
927
+ } else {
928
+ $vars = [
929
+ 'id' => 'tsf-pt',
930
+ 'name' => 'pt',
931
+ ];
932
+ $deps = [ 'jquery', 'tsf', 'tsf-post', 'tsf-tt' ];
933
+ }
934
+
935
+ return [
936
+ [
937
+ 'id' => $vars['id'],
938
+ 'type' => 'js',
939
+ 'deps' => $deps,
940
+ 'autoload' => true,
941
+ 'name' => $vars['name'],
942
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
943
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
944
+ 'l10n' => [
945
+ 'name' => 'tsfPTL10n',
946
+ 'data' => [
947
+ 'taxonomies' => $taxonomies,
948
+ ],
949
+ ],
950
+ 'tmpl' => [
951
+ 'file' => $tsf->get_view_location( 'templates/inpost/primary-term-selector' ),
952
+ ],
953
+ ],
954
+ [
955
+ 'id' => 'tsf-pt',
956
+ 'type' => 'css',
957
+ 'deps' => [ 'tsf-tt' ],
958
+ 'autoload' => true,
959
+ 'hasrtl' => false,
960
+ 'name' => 'pt',
961
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
962
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
963
+ 'inline' => $inline_css,
964
+ ],
965
+ ];
966
+ }
967
+
968
+ /**
969
+ * Returns the Pixel and Character counter script params.
970
+ *
971
+ * @since 4.0.0
972
+ *
973
+ * @return array The script params.
974
+ */
975
+ public static function get_counter_scripts() {
976
+
977
+ $tsf = \the_seo_framework();
978
+
979
+ return [
980
+ [
981
+ 'id' => 'tsf-c',
982
+ 'type' => 'js',
983
+ 'deps' => [ 'jquery', 'tsf-tt', 'tsf' ],
984
+ 'autoload' => true,
985
+ 'name' => 'c',
986
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/js/',
987
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
988
+ 'l10n' => [
989
+ 'name' => 'tsfCL10n',
990
+ 'data' => [
991
+ 'guidelines' => $tsf->get_input_guidelines(),
992
+ 'counterType' => \absint( $tsf->get_user_option( 0, 'counter_type', 3 ) ),
993
+ 'i18n' => [
994
+ 'guidelines' => $tsf->get_input_guidelines_i18n(),
995
+ /* translators: Pixel counter. 1: number (value), 2: number (guideline) */
996
+ 'pixelsUsed' => \esc_attr__( '%1$d out of %2$d pixels are used.', 'autodescription' ),
997
+ ],
998
+ ],
999
+ ],
1000
+ ],
1001
+ [
1002
+ 'id' => 'tsf-c',
1003
+ 'type' => 'css',
1004
+ 'deps' => [ 'tsf-tt' ],
1005
+ 'autoload' => true,
1006
+ 'hasrtl' => false,
1007
+ 'name' => 'tsfc',
1008
+ 'base' => THE_SEO_FRAMEWORK_DIR_URL . 'lib/css/',
1009
+ 'ver' => THE_SEO_FRAMEWORK_VERSION,
1010
+ ],
1011
+ ];
1012
+ }
1013
+ }
1014
+
1015
+ $_load_scripts_class();
inc/classes/bridges/seobar.class.php ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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
+ * 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
41
+ * @var string The column name.
42
+ */
43
+ private $column_name = 'tsf-seo-bar-wrap';
44
+
45
+ /**
46
+ * Adds SEO column on edit(-tags).php
47
+ *
48
+ * Also determines where the column should be placed. Preferred before comments, then data, then tags.
49
+ * When none found, it will add the column to the end.
50
+ *
51
+ * @since 4.0.0
52
+ * @access private
53
+ * @abstract
54
+ *
55
+ * @param array $columns The existing columns.
56
+ * @return array $columns The adjusted columns.
57
+ */
58
+ public function _add_column( $columns ) {
59
+
60
+ $seocolumn = [ $this->column_name => 'SEO' ];
61
+
62
+ $column_keys = array_keys( $columns );
63
+
64
+ //* Column keys to look for, in order of appearance.
65
+ $order_keys = [
66
+ 'comments',
67
+ 'posts',
68
+ 'date',
69
+ 'tags',
70
+ ];
71
+
72
+ /**
73
+ * @since 2.8.0
74
+ * @param array $order_keys The keys where the SEO column may be prepended to.
75
+ * The first key found will be used.
76
+ */
77
+ $order_keys = (array) \apply_filters( 'the_seo_framework_seo_column_keys_order', $order_keys );
78
+
79
+ foreach ( $order_keys as $key ) {
80
+ //* Put value in $offset, if not false, break loop.
81
+ $offset = array_search( $key, $column_keys, true );
82
+ if ( false !== $offset )
83
+ break;
84
+ }
85
+
86
+ //* It tried but found nothing
87
+ if ( false === $offset ) {
88
+ //* Add SEO bar at the end of columns.
89
+ $columns = array_merge( $columns, $seocolumn );
90
+ } else {
91
+ //* Add seo bar between columns.
92
+
93
+ //* Cache columns.
94
+ $columns_before = $columns;
95
+
96
+ $columns = array_merge(
97
+ array_splice( $columns, 0, $offset ),
98
+ $seocolumn,
99
+ array_splice( $columns_before, $offset )
100
+ );
101
+ }
102
+
103
+ return $columns;
104
+ }
105
+
106
+ /**
107
+ * Outputs the SEO Bar for posts and pages.
108
+ *
109
+ * @since 4.0.0
110
+ * @access private
111
+ * @abstract
112
+ *
113
+ * @param string $column_name The name of the column to display.
114
+ * @param int $post_id The current post ID.
115
+ */
116
+ public function _output_column_contents_for_post( $column_name, $post_id ) {
117
+
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
+ ] );
125
+
126
+ if ( $this->doing_ajax )
127
+ echo $this->get_seo_bar_ajax_script(); // phpcs:ignore, WordPress.Security.EscapeOutput
128
+ }
129
+
130
+ /**
131
+ * Returns the SEO Bar for terms.
132
+ *
133
+ * @since 4.0.0
134
+ * @access private
135
+ * @abstract
136
+ * @NOTE Unlike _output_seo_bar_for_column(), this is a filter callback.
137
+ * Because of this, the first parameter is a useless string, which must be extended.
138
+ * Discrepancy: https://core.trac.wordpress.org/ticket/33521
139
+ * With this, the proper function name should be "_get..." or "_add...", but not "_output.."
140
+ *
141
+ * @param string $string Blank string.
142
+ * @param string $column_name Name of the column.
143
+ * @param string $term_id Term ID.
144
+ * @return string
145
+ */
146
+ public function _output_column_contents_for_term( $string, $column_name, $term_id ) {
147
+
148
+ if ( $this->column_name !== $column_name ) return $string;
149
+
150
+ if ( $this->doing_ajax )
151
+ $string .= $this->get_seo_bar_ajax_script();
152
+
153
+ return \The_SEO_Framework\Interpreters\SeoBar::generate_bar( [
154
+ 'id' => $term_id,
155
+ 'taxonomy' => $this->taxonomy,
156
+ ] ) . $string;
157
+ }
158
+
159
+ /**
160
+ * Outputs a JS script that triggers SEO Bar updates.
161
+ * This is a necessity as WordPress doesn't trigger actions on update.
162
+ *
163
+ * TODO bind to WordPress' function instead? Didn't we already do that?!
164
+ * See: `tsfLe._hijackListeners()`; Although, that doesn't cover "adding" new items.
165
+ *
166
+ * @since 4.0.0
167
+ *
168
+ * @return string The triggering script.
169
+ */
170
+ private function get_seo_bar_ajax_script() {
171
+ return "<script>'use strict';(()=>document.dispatchEvent(new Event('tsfLeUpdated')))();</script>";
172
+ }
173
+ }
inc/classes/bridges/seosettings.class.php ADDED
@@ -0,0 +1,746 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Bridges\SeoSettings
4
+ * @subpackage The_SEO_Framework\Admin\Settings
5
+ */
6
+
7
+ namespace The_SEO_Framework\Bridges;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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
+ * Prepares the SEO Settings page interface.
30
+ *
31
+ * Note the use of "metabox" instead of "meta_box" throughout.
32
+ *
33
+ * @since 4.0.0
34
+ * @access protected
35
+ * @internal
36
+ * @final Can't be extended.
37
+ */
38
+ final class SeoSettings {
39
+ use \The_SEO_Framework\Traits\Enclose_Stray_Private;
40
+
41
+ /**
42
+ * Registers meta boxes on the Site SEO Settings page.
43
+ *
44
+ * @since 4.0.0
45
+ * @access private
46
+ */
47
+ public static function _register_seo_settings_meta_boxes() {
48
+
49
+ /**
50
+ * Various metabox filters.
51
+ * Set any to false if you wish the meta box to be removed.
52
+ *
53
+ * @since 2.2.4
54
+ * @since 2.8.0: Added `the_seo_framework_general_metabox` filter.
55
+ */
56
+ $general = (bool) \apply_filters( 'the_seo_framework_general_metabox', true );
57
+ $title = (bool) \apply_filters( 'the_seo_framework_title_metabox', true );
58
+ $description = (bool) \apply_filters( 'the_seo_framework_description_metabox', true );
59
+ $robots = (bool) \apply_filters( 'the_seo_framework_robots_metabox', true );
60
+ $home = (bool) \apply_filters( 'the_seo_framework_home_metabox', true );
61
+ $social = (bool) \apply_filters( 'the_seo_framework_social_metabox', true );
62
+ $schema = (bool) \apply_filters( 'the_seo_framework_schema_metabox', true );
63
+ $webmaster = (bool) \apply_filters( 'the_seo_framework_webmaster_metabox', true );
64
+ $sitemap = (bool) \apply_filters( 'the_seo_framework_sitemap_metabox', true );
65
+ $feed = (bool) \apply_filters( 'the_seo_framework_feed_metabox', true );
66
+
67
+ $settings_page_hook = \the_seo_framework()->seo_settings_page_hook;
68
+
69
+ //* General Meta Box
70
+ if ( $general )
71
+ \add_meta_box(
72
+ 'autodescription-general-settings',
73
+ \esc_html__( 'General Settings', 'autodescription' ),
74
+ __CLASS__ . '::_general_metabox',
75
+ $settings_page_hook,
76
+ 'main',
77
+ []
78
+ );
79
+
80
+ //* Title Meta Box
81
+ if ( $title )
82
+ \add_meta_box(
83
+ 'autodescription-title-settings',
84
+ \esc_html__( 'Title Settings', 'autodescription' ),
85
+ __CLASS__ . '::_title_metabox',
86
+ $settings_page_hook,
87
+ 'main',
88
+ []
89
+ );
90
+
91
+ //* Description Meta Box
92
+ if ( $description )
93
+ \add_meta_box(
94
+ 'autodescription-description-settings',
95
+ \esc_html__( 'Description Meta Settings', 'autodescription' ),
96
+ __CLASS__ . '::_description_metabox',
97
+ $settings_page_hook,
98
+ 'main',
99
+ []
100
+ );
101
+
102
+ //* Homepage Meta Box
103
+ if ( $home )
104
+ \add_meta_box(
105
+ 'autodescription-homepage-settings',
106
+ \esc_html__( 'Homepage Settings', 'autodescription' ),
107
+ __CLASS__ . '::_homepage_metabox',
108
+ $settings_page_hook,
109
+ 'main',
110
+ []
111
+ );
112
+
113
+ //* Social Meta Box
114
+ if ( $social )
115
+ \add_meta_box(
116
+ 'autodescription-social-settings',
117
+ \esc_html__( 'Social Meta Settings', 'autodescription' ),
118
+ __CLASS__ . '::_social_metabox',
119
+ $settings_page_hook,
120
+ 'main',
121
+ []
122
+ );
123
+
124
+ //* Schema Meta Box
125
+ if ( $schema )
126
+ \add_meta_box(
127
+ 'autodescription-schema-settings',
128
+ \esc_html__( 'Schema.org Settings', 'autodescription' ),
129
+ __CLASS__ . '::_schema_metabox',
130
+ $settings_page_hook,
131
+ 'main',
132
+ []
133
+ );
134
+
135
+ //* Robots Meta Box
136
+ if ( $robots )
137
+ \add_meta_box(
138
+ 'autodescription-robots-settings',
139
+ \esc_html__( 'Robots Meta Settings', 'autodescription' ),
140
+ __CLASS__ . '::_robots_metabox',
141
+ $settings_page_hook,
142
+ 'main',
143
+ []
144
+ );
145
+
146
+ //* Webmaster Meta Box
147
+ if ( $webmaster )
148
+ \add_meta_box(
149
+ 'autodescription-webmaster-settings',
150
+ \esc_html__( 'Webmaster Meta Settings', 'autodescription' ),
151
+ __CLASS__ . '::_webmaster_metabox',
152
+ $settings_page_hook,
153
+ 'main',
154
+ []
155
+ );
156
+
157
+ //* Sitemaps Meta Box
158
+ if ( $sitemap )
159
+ \add_meta_box(
160
+ 'autodescription-sitemap-settings',
161
+ \esc_html__( 'Sitemap Settings', 'autodescription' ),
162
+ __CLASS__ . '::_sitemaps_metabox',
163
+ $settings_page_hook,
164
+ 'main',
165
+ []
166
+ );
167
+
168
+ //* Feed Meta Box
169
+ if ( $feed )
170
+ \add_meta_box(
171
+ 'autodescription-feed-settings',
172
+ \esc_html__( 'Feed Settings', 'autodescription' ),
173
+ __CLASS__ . '::_feed_metabox',
174
+ $settings_page_hook,
175
+ 'main',
176
+ []
177
+ );
178
+ }
179
+
180
+ /**
181
+ * Setting nav tab wrappers.
182
+ * Outputs Tabs and settings content.
183
+ *
184
+ * @since 4.0.0
185
+ * @access private
186
+ *
187
+ * @param string $id The nav-tab ID.
188
+ * @param array $tabs The tab content {
189
+ * string tab ID => array : {
190
+ * string name : Tab name.
191
+ * callable callback : Output function.
192
+ * string dashicon : The dashicon to use.
193
+ * mixed args : Optional callback function args.
194
+ * }
195
+ * }
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 ) {
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
+ /**
204
+ * Outputs SEO Settings page wrap.
205
+ *
206
+ * @since 4.0.0
207
+ * @access private
208
+ */
209
+ public static function _output_wrap() {
210
+ /**
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
+ */
218
+ \do_action( 'the_seo_framework_pro_seo_settings' );
219
+ }
220
+
221
+ /**
222
+ * Outputs SEO Settings columns.
223
+ *
224
+ * @since 4.0.0
225
+ * @access private
226
+ */
227
+ public static function _output_columns() {
228
+ \the_seo_framework()->get_view( 'admin/seo-settings-columns' );
229
+ }
230
+
231
+ /**
232
+ * Outputs General Settings meta box on the Site SEO Settings page.
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
+ */
249
+ \do_action( 'the_seo_framework_general_metabox_after' );
250
+ }
251
+
252
+ /**
253
+ * Outputs General Settings meta box general tab.
254
+ *
255
+ * @since 4.0.0
256
+ * @access private
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
+ /**
264
+ * Outputs General Settings meta box layout tab.
265
+ *
266
+ * @since 4.0.0
267
+ * @access private
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
+ /**
275
+ * Outputs General Settings meta box performance tab.
276
+ *
277
+ * @since 4.0.0
278
+ * @access private
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
+ /**
286
+ * Outputs General Settings meta box canonical tab.
287
+ *
288
+ * @since 4.0.0
289
+ * @access private
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
+ /**
297
+ * Outputs General Settings meta box timestamps tab.
298
+ *
299
+ * @since 4.0.0
300
+ * @access private
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
+ /**
308
+ * Outputs General Settings meta box post types tab.
309
+ *
310
+ * @since 4.0.0
311
+ * @access private
312
+ * @see static::general_metabox() : Callback for General Settings box.
313
+ */
314
+ public static function _general_metabox_posttypes_tab() {
315
+ \the_seo_framework()->get_view( 'admin/metaboxes/general-metabox', [], 'posttypes' );
316
+ }
317
+
318
+ /**
319
+ * Title meta box on the Site SEO Settings page.
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
+ */
336
+ \do_action( 'the_seo_framework_title_metabox_after' );
337
+ }
338
+
339
+ /**
340
+ * Title meta box general tab.
341
+ *
342
+ * @since 4.0.0
343
+ * @access private
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
+ /**
351
+ * Title meta box general tab.
352
+ *
353
+ * @since 4.0.0
354
+ * @access private
355
+ * @see static::title_metabox() : Callback for Title Settings box.
356
+ *
357
+ * @param array $examples : array {
358
+ * 'left' => Left Example
359
+ * 'right' => Right Example
360
+ * }
361
+ */
362
+ public static function _title_metabox_additions_tab( $examples = [] ) {
363
+ \the_seo_framework()->get_view( 'admin/metaboxes/title-metabox', get_defined_vars(), 'additions' );
364
+ }
365
+
366
+ /**
367
+ * Title meta box prefixes tab.
368
+ *
369
+ * @since 4.0.0
370
+ * @access private
371
+ * @see static::title_metabox() : Callback for Title Settings box.
372
+ *
373
+ * @param array $additions : array {
374
+ * 'left' => Left Example Addtitions
375
+ * 'right' => Right Example Additions
376
+ * }
377
+ * @param bool $showleft The example location.
378
+ */
379
+ public static function _title_metabox_prefixes_tab( $additions = [], $showleft = false ) {
380
+ \the_seo_framework()->get_view( 'admin/metaboxes/title-metabox', get_defined_vars(), 'prefixes' );
381
+ }
382
+
383
+ /**
384
+ * Description meta box on the Site SEO Settings page.
385
+ *
386
+ * @since 4.0.0
387
+ * @access private
388
+ *
389
+ * @param \WP_Post|null $post The current post object.
390
+ * @param array $args The metabox arguments.
391
+ */
392
+ public static function _description_metabox( $post = null, $args = [] ) {
393
+ /**
394
+ * @since 2.5.0 or earlier.
395
+ */
396
+ \do_action( 'the_seo_framework_description_metabox_before' );
397
+ \the_seo_framework()->get_view( 'admin/metaboxes/description-metabox', $args );
398
+ /**
399
+ * @since 2.5.0 or earlier.
400
+ */
401
+ \do_action( 'the_seo_framework_description_metabox_after' );
402
+ }
403
+
404
+ /**
405
+ * Robots meta box on the Site SEO Settings page.
406
+ *
407
+ * @since 4.0.0
408
+ * @access private
409
+ *
410
+ * @param \WP_Post|null $post The current post object.
411
+ * @param array $args The metabox arguments.
412
+ */
413
+ public static function _robots_metabox( $post = null, $args = [] ) {
414
+ /**
415
+ * @since 2.5.0 or earlier.
416
+ */
417
+ \do_action( 'the_seo_framework_robots_metabox_before' );
418
+ \the_seo_framework()->get_view( 'admin/metaboxes/robots-metabox', $args );
419
+ /**
420
+ * @since 2.5.0 or earlier.
421
+ */
422
+ \do_action( 'the_seo_framework_robots_metabox_after' );
423
+ }
424
+
425
+ /**
426
+ * Robots Metabox General Tab output.
427
+ *
428
+ * @since 4.0.0
429
+ * @access private
430
+ * @see static::robots_metabox() Callback for Robots Settings box.
431
+ */
432
+ public static function _robots_metabox_general_tab() {
433
+ \the_seo_framework()->get_view( 'admin/metaboxes/robots-metabox', [], 'general' );
434
+ }
435
+
436
+ /**
437
+ * Robots Metabox "No-: Index/Follow/Archive" Tab output.
438
+ *
439
+ * @since 4.0.0
440
+ * @access private
441
+ * @see static::robots_metabox() Callback for Robots Settings box.
442
+ *
443
+ * @param array $types The non-default robots exclusion types (date, author, etc.).
444
+ * @param array $post_types The post types.
445
+ * @param array $robots The robots option values : {
446
+ * 'value' string The robots option value.
447
+ * 'name' string The robots name.
448
+ * 'desc' string Explains what the robots type does.
449
+ * }
450
+ */
451
+ public static function _robots_metabox_no_tab( $types, $post_types, $robots ) {
452
+ \the_seo_framework()->get_view( 'admin/metaboxes/robots-metabox', get_defined_vars(), 'no' );
453
+ }
454
+
455
+ /**
456
+ * Outputs the Homepage meta box on the Site SEO Settings page.
457
+ *
458
+ * @since 4.0.0
459
+ * @access private
460
+ *
461
+ * @param \WP_Post|null $post The current post object.
462
+ * @param array $args The navigation tabs args.
463
+ */
464
+ public static function _homepage_metabox( $post = null, $args = [] ) {
465
+ /**
466
+ * @since 2.5.0 or earlier.
467
+ */
468
+ \do_action( 'the_seo_framework_homepage_metabox_before' );
469
+ \the_seo_framework()->get_view( 'admin/metaboxes/homepage-metabox', $args );
470
+ /**
471
+ * @since 2.5.0 or earlier.
472
+ */
473
+ \do_action( 'the_seo_framework_homepage_metabox_after' );
474
+ }
475
+
476
+ /**
477
+ * Homepage Metabox General 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_general_tab() {
484
+ \the_seo_framework()->get_view( 'admin/metaboxes/homepage-metabox', [], 'general' );
485
+ }
486
+
487
+ /**
488
+ * Homepage Metabox Additions 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_additions_tab() {
495
+ \the_seo_framework()->get_view( 'admin/metaboxes/homepage-metabox', [], 'additions' );
496
+ }
497
+
498
+ /**
499
+ * Homepage Metabox Robots Tab Output
500
+ *
501
+ * @since 4.0.0
502
+ * @access private
503
+ * @see static::homepage_metabox() Callback for Homepage Settings box.
504
+ */
505
+ public static function _homepage_metabox_robots_tab() {
506
+ \the_seo_framework()->get_view( 'admin/metaboxes/homepage-metabox', [], 'robots' );
507
+ }
508
+
509
+ /**
510
+ * Homepage Metabox Social Tab Output
511
+ *
512
+ * @since 4.0.0
513
+ * @access private
514
+ * @see static::homepage_metabox() Callback for Homepage Settings box.
515
+ */
516
+ public static function _homepage_metabox_social_tab() {
517
+ \the_seo_framework()->get_view( 'admin/metaboxes/homepage-metabox', [], 'social' );
518
+ }
519
+
520
+ /**
521
+ * Social meta box on the Site SEO Settings page.
522
+ *
523
+ * @since 4.0.0
524
+ * @access private
525
+ *
526
+ * @param \WP_Post|null $post The current post object.
527
+ * @param array $args The navigation tabs args.
528
+ */
529
+ public static function _social_metabox( $post = null, $args = [] ) {
530
+ /**
531
+ * @since 2.5.0 or earlier.
532
+ */
533
+ \do_action( 'the_seo_framework_social_metabox_before' );
534
+ \the_seo_framework()->get_view( 'admin/metaboxes/social-metabox', $args );
535
+ /**
536
+ * @since 2.5.0 or earlier.
537
+ */
538
+ \do_action( 'the_seo_framework_social_metabox_after' );
539
+ }
540
+
541
+ /**
542
+ * Social Metabox General Tab output.
543
+ *
544
+ * @since 4.0.0
545
+ * @access private
546
+ * @see static::social_metabox() Callback for Social Settings box.
547
+ */
548
+ public static function _social_metabox_general_tab() {
549
+ \the_seo_framework()->get_view( 'admin/metaboxes/social-metabox', [], 'general' );
550
+ }
551
+
552
+ /**
553
+ * Social Metabox Facebook Tab output.
554
+ *
555
+ * @since 4.0.0
556
+ * @access private
557
+ * @see static::social_metabox() Callback for Social Settings box.
558
+ */
559
+ public static function _social_metabox_facebook_tab() {
560
+ \the_seo_framework()->get_view( 'admin/metaboxes/social-metabox', [], 'facebook' );
561
+ }
562
+
563
+ /**
564
+ * Social Metabox Twitter Tab output.
565
+ *
566
+ * @since 4.0.0
567
+ * @access private
568
+ * @see static::social_metabox() Callback for Social Settings box.
569
+ */
570
+ public static function _social_metabox_twitter_tab() {
571
+ \the_seo_framework()->get_view( 'admin/metaboxes/social-metabox', [], 'twitter' );
572
+ }
573
+
574
+ /**
575
+ * Social Metabox PostDates Tab output.
576
+ *
577
+ * @since 4.0.0
578
+ * @access private
579
+ * @see static::social_metabox() Callback for Social Settings box.
580
+ */
581
+ public static function _social_metabox_postdates_tab() {
582
+ \the_seo_framework()->get_view( 'admin/metaboxes/social-metabox', [], 'postdates' );
583
+ }
584
+
585
+ /**
586
+ * Webmaster meta box on the Site SEO Settings page.
587
+ *
588
+ * @since 4.0.0
589
+ * @access private
590
+ *
591
+ * @param \WP_Post|null $post The current post object.
592
+ * @param array $args The navigation tabs args.
593
+ */
594
+ public static function _webmaster_metabox( $post = null, $args = [] ) {
595
+ /**
596
+ * @since 2.5.0 or earlier.
597
+ */
598
+ \do_action( 'the_seo_framework_webmaster_metabox_before' );
599
+ \the_seo_framework()->get_view( 'admin/metaboxes/webmaster-metabox', $args );
600
+ /**
601
+ * @since 2.5.0 or earlier.
602
+ */
603
+ \do_action( 'the_seo_framework_webmaster_metabox_after' );
604
+ }
605
+
606
+ /**
607
+ * Sitemaps meta box on the Site SEO Settings page.
608
+ *
609
+ * @since 4.0.0
610
+ * @access private
611
+ * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
612
+ *
613
+ * @param \WP_Post|null $post The current post object.
614
+ * @param array $args The navigation tabs args.
615
+ */
616
+ public static function _sitemaps_metabox( $post = null, $args = [] ) {
617
+ /**
618
+ * @since 2.5.0 or earlier.
619
+ */
620
+ \do_action( 'the_seo_framework_sitemaps_metabox_before' );
621
+ \the_seo_framework()->get_view( 'admin/metaboxes/sitemaps-metabox', $args );
622
+ /**
623
+ * @since 2.5.0 or earlier.
624
+ */
625
+ \do_action( 'the_seo_framework_sitemaps_metabox_after' );
626
+ }
627
+
628
+ /**
629
+ * Sitemaps Metabox General Tab output.
630
+ *
631
+ * @since 4.0.0
632
+ * @access private
633
+ * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
634
+ */
635
+ public static function _sitemaps_metabox_general_tab() {
636
+ \the_seo_framework()->get_view( 'admin/metaboxes/sitemaps-metabox', [], 'general' );
637
+ }
638
+
639
+ /**
640
+ * Sitemaps Metabox Robots Tab output.
641
+ *
642
+ * @since 4.0.0
643
+ * @access private
644
+ * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
645
+ */
646
+ public static function _sitemaps_metabox_robots_tab() {
647
+ \the_seo_framework()->get_view( 'admin/metaboxes/sitemaps-metabox', [], 'robots' );
648
+ }
649
+
650
+ /**
651
+ * Sitemaps Metabox Metadata Tab output.
652
+ *
653
+ * @since 4.0.0
654
+ * @access private
655
+ * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
656
+ */
657
+ public static function _sitemaps_metabox_metadata_tab() {
658
+ \the_seo_framework()->get_view( 'admin/metaboxes/sitemaps-metabox', [], 'metadata' );
659
+ }
660
+
661
+ /**
662
+ * Sitemaps Metabox Notify Tab output.
663
+ *
664
+ * @since 4.0.0
665
+ * @access private
666
+ * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
667
+ */
668
+ public static function _sitemaps_metabox_notify_tab() {
669
+ \the_seo_framework()->get_view( 'admin/metaboxes/sitemaps-metabox', [], 'notify' );
670
+ }
671
+
672
+ /**
673
+ * Sitemaps Metabox Style Tab output.
674
+ *
675
+ * @since 4.0.0
676
+ * @access private
677
+ * @see static::sitemaps_metabox() Callback for Sitemaps Settings box.
678
+ */
679
+ public static function _sitemaps_metabox_style_tab() {
680
+ \the_seo_framework()->get_view( 'admin/metaboxes/sitemaps-metabox', [], 'style' );
681
+ }
682
+
683
+ /**
684
+ * Feed Metabox on the Site SEO Settings page.
685
+ *
686
+ * @since 4.0.0
687
+ * @access private
688
+ *
689
+ * @param \WP_Post|null $post The current post object.
690
+ * @param array $args The navigation tabs args.
691
+ */
692
+ public static function _feed_metabox( $post = null, $args = [] ) {
693
+ /**
694
+ * @since 2.5.2
695
+ */
696
+ \do_action( 'the_seo_framework_feed_metabox_before' );
697
+ \the_seo_framework()->get_view( 'admin/metaboxes/feed-metabox', $args );
698
+ /**
699
+ * @since 2.5.2
700
+ */
701
+ \do_action( 'the_seo_framework_feed_metabox_after' );
702
+ }
703
+
704
+ /**
705
+ * Schema Metabox on the Site SEO Settings page.
706
+ *
707
+ * @since 4.0.0
708
+ * @access private
709
+ *
710
+ * @param \WP_Post|null $post The current post object.
711
+ * @param array $args The navigation tabs args.
712
+ */
713
+ public static function _schema_metabox( $post = null, $args = [] ) {
714
+ /**
715
+ * @since 2.6.0
716
+ */
717
+ \do_action( 'the_seo_framework_schema_metabox_before' );
718
+ \the_seo_framework()->get_view( 'admin/metaboxes/schema-metabox', $args );
719
+ /**
720
+ * @since 2.6.0
721
+ */
722
+ \do_action( 'the_seo_framework_schema_metabox_after' );
723
+ }
724
+
725
+ /**
726
+ * Schema Metabox Structure Tab output.
727
+ *
728
+ * @since 4.0.0
729
+ * @access private
730
+ * @see static::schema_metabox() Callback for Schema.org Settings box.
731
+ */
732
+ public static function _schema_metabox_structure_tab() {
733
+ \the_seo_framework()->get_view( 'admin/metaboxes/schema-metabox', [], 'structure' );
734
+ }
735
+
736
+ /**
737
+ * Schema Metabox PResence Tab output.
738
+ *
739
+ * @since 4.0.0
740
+ * @access private
741
+ * @see static::schema_metabox() Callback for Schema.org Settings box.
742
+ */
743
+ public static function _schema_metabox_presence_tab() {
744
+ \the_seo_framework()->get_view( 'admin/metaboxes/schema-metabox', [], 'presence' );
745
+ }
746
+ }
inc/classes/bridges/sitemap.class.php ADDED
@@ -0,0 +1,511 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Bridges\Sitemap
4
+ * @subpackage The_SEO_Framework\Sitemap
5
+ */
6
+
7
+ namespace The_SEO_Framework\Bridges;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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
+ * 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
+ new Sitemap();
37
+ };
38
+
39
+ /**
40
+ * Prepares sitemap output.
41
+ *
42
+ * @since 4.0.0
43
+ * @access protected
44
+ * @final Can't be extended.
45
+ */
46
+ final class Sitemap {
47
+ use \The_SEO_Framework\Traits\Enclose_Stray_Private;
48
+
49
+ /**
50
+ * @since 4.0.0
51
+ * @var \The_SEO_Framework\Bridges\Sitemap
52
+ */
53
+ private static $instance;
54
+
55
+ /**
56
+ * @var null|\The_SEO_Framework\Load
57
+ */
58
+ private static $tsf = null;
59
+
60
+ /**
61
+ * Returns this instance.
62
+ *
63
+ * @since 4.0.0
64
+ *
65
+ * @return \The_SEO_Framework\Bridges\Sitemap $instance
66
+ */
67
+ public static function get_instance() {
68
+ return static::$instance;
69
+ }
70
+
71
+ /**
72
+ * Prepares the class and loads constructor.
73
+ *
74
+ * Use this if the actions need to be registered early, but nothing else of
75
+ * this class is needed yet.
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.
83
+ *
84
+ * This probably autoloads at action "admin_enqueue_scripts", priority "0".
85
+ *
86
+ * @since 4.0.0
87
+ * @access private
88
+ * @staticvar int $count Enforces singleton.
89
+ * @internal
90
+ */
91
+ public function __construct() {
92
+
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
+ /**
101
+ * Initializes scripts based on admin query.
102
+ *
103
+ * @since 4.0.0
104
+ * @since 4.0.2 Can now parse non-ASCII URLs. No longer lowercases raw URIs.
105
+ * @access private
106
+ * @internal This always runs; build your own loader from the public methods, instead.
107
+ */
108
+ public function _init() {
109
+
110
+ // The raw path(+query) of the requested URI.
111
+ if ( isset( $_SERVER['REQUEST_URI'] ) ) {
112
+ $raw_uri = rawurldecode(
113
+ \wp_check_invalid_utf8(
114
+ stripslashes( $_SERVER['REQUEST_URI'] )
115
+ )
116
+ ) ?: '/';
117
+ } else {
118
+ $raw_uri = '/';
119
+ }
120
+
121
+ // Probably home page.
122
+ if ( '/' === $raw_uri ) return;
123
+
124
+ $sitemap_id = $this->get_sitemap_id_from_uri( $raw_uri );
125
+
126
+ if ( ! $sitemap_id ) return;
127
+
128
+ // Don't let WordPress think this is 404.
129
+ $GLOBALS['wp_query']->is_404 = false;
130
+
131
+ static::$tsf->is_sitemap( true );
132
+
133
+ /**
134
+ * Set at least 2000 variables free.
135
+ * Freeing 0.15MB on a clean WordPress installation on PHP 7.
136
+ */
137
+ $this->clean_up_globals();
138
+
139
+ /**
140
+ * @since 4.0.0
141
+ * @param string $sitemap_id The sitemap ID. See `static::get_sitemap_endpoint_list()`.
142
+ */
143
+ \do_action( 'the_seo_framework_sitemap_header', $sitemap_id );
144
+
145
+ call_user_func( $this->get_sitemap_endpoint_list()[ $sitemap_id ]['callback'], $sitemap_id );
146
+ }
147
+
148
+ /**
149
+ * Returns the expected sitemap endpoint for the given ID.
150
+ *
151
+ * @since 4.0.0
152
+ * @global \WP_Rewrite $wp_rewrite
153
+ *
154
+ * @param string $id The base ID. Default 'base'.
155
+ * @return string|bool False if ID isn't registered; the URL otherwise.
156
+ */
157
+ public function get_expected_sitemap_endpoint_url( $id = 'base' ) {
158
+
159
+ $list = $this->get_sitemap_endpoint_list();
160
+
161
+ if ( ! isset( $list[ $id ] ) ) return false;
162
+
163
+ global $wp_rewrite;
164
+
165
+ $scheme = static::$tsf->get_preferred_scheme();
166
+ $prefix = $this->get_sitemap_path_prefix();
167
+
168
+ if ( $wp_rewrite->using_index_permalinks() ) {
169
+ $url = \home_url( "/index.php$prefix{$list[ $id ]['endpoint']}", $scheme );
170
+ } elseif ( $wp_rewrite->using_permalinks() ) {
171
+ $url = \home_url( "$prefix{$list[ $id ]['endpoint']}", $scheme );
172
+ } else {
173
+ $url = \home_url( "$prefix?tsf-sitemap=$id", $scheme );
174
+ }
175
+
176
+ return \esc_url_raw( $url );
177
+ }
178
+
179
+ /**
180
+ * Returns a list of known sitemap endpoints.
181
+ *
182
+ * @since 4.0.0
183
+ * @static array $list
184
+ *
185
+ * @return array The sitemap endpoints with their callbacks.
186
+ */
187
+ public function get_sitemap_endpoint_list() {
188
+ static $list;
189
+ /**
190
+ * @since 4.0.0
191
+ * @since 4.0.2 Made the endpoints' regex case-insensitive.
192
+ * @link Example: https://github.com/sybrew/tsf-term-sitemap
193
+ * @param array $list The endpoints: {
194
+ * 'id' => array: {
195
+ * 'endpoint' => string The expected "pretty" endpoint, meant for administrative display.
196
+ * 'epregex' => string The endpoint regex, following the home path regex.
197
+ * N.B. Be wary of case sensitivity. Append the i-flag.
198
+ * N.B. Trailing slashes will cause the match to fail.
199
+ * N.B. Use ASCII-endpoints only. Don't play with UTF-8 or translation strings.
200
+ * 'callback' => callable The callback for the sitemap output.
201
+ * Tip: You can pass arbitrary indexes. Prefix them with an underscore to ensure forward compatibility.
202
+ * Tip: In the callback, use
203
+ * `\The_SEO_Framework\Bridges\Sitemap::get_instance()->get_sitemap_endpoint_list()[$sitemap_id]`
204
+ * It returns the arguments you've passed in this filter; including your arbitrary indexes.
205
+ * 'robots' => bool Whether the endpoint should be mentioned in the robots.txt file.
206
+ * }
207
+ * }
208
+ */
209
+ return $list = $list ?: \apply_filters(
210
+ 'the_seo_framework_sitemap_endpoint_list',
211
+ [
212
+ 'base' => [
213
+ 'endpoint' => 'sitemap.xml',
214
+ 'regex' => '/^sitemap\.xml/i',
215
+ 'callback' => static::class . '::output_base_sitemap',
216
+ 'robots' => true,
217
+ ],
218
+ 'index' => [
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
+ /**
235
+ * Outputs sitemap.xml 'file' and header.
236
+ *
237
+ * @since 2.2.9
238
+ * @since 3.1.0 1. Now outputs 200-response code.
239
+ * 2. Now outputs robots tag, preventing indexing.
240
+ * 3. Now overrides other header tags.
241
+ * @since 4.0.0 1. Moved to \The_SEO_Framework\Bridges\Sitemap
242
+ * 2. Renamed from `output_sitemap()`
243
+ */
244
+ public function output_base_sitemap() {
245
+
246
+ //* Remove output, if any.
247
+ static::$tsf->clean_response_header();
248
+
249
+ if ( ! headers_sent() ) {
250
+ \status_header( 200 );
251
+ header( 'Content-type: text/xml; charset=utf-8', true );
252
+ }
253
+
254
+ //* Fetch sitemap content and add trailing line. Already escaped internally.
255
+ static::$tsf->get_view( 'sitemap/xml-sitemap' );
256
+ echo "\n";
257
+
258
+ // We're done now.
259
+ exit;
260
+ }
261
+
262
+ /**
263
+ * Sitemap XSL stylesheet output.
264
+ *
265
+ * @since 2.8.0
266
+ * @since 3.1.0 1. Now outputs 200-response code.
267
+ * 2. Now outputs robots tag, preventing indexing.
268
+ * 3. Now overrides other header tags.
269
+ * @since 4.0.0 1. Moved to \The_SEO_Framework\Bridges\Sitemap
270
+ * 2. Renamed from `output_sitemap_xsl_stylesheet()`
271
+ */
272
+ public function output_stylesheet() {
273
+
274
+ static::$tsf->clean_response_header();
275
+
276
+ if ( ! headers_sent() ) {
277
+ \status_header( 200 );
278
+ header( 'Content-type: text/xsl; charset=utf-8', true );
279
+ header( 'Cache-Control: max-age=1800', true );
280
+ }
281
+
282
+ static::$tsf->get_view( 'sitemap/xsl-stylesheet' );
283
+ exit;
284
+ }
285
+
286
+ /**
287
+ * Outputs the sitemap header.
288
+ *
289
+ * @since 4.0.0
290
+ */
291
+ public function output_sitemap_header() {
292
+
293
+ echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
294
+
295
+ if ( static::$tsf->get_option( 'sitemap_styles' ) ) {
296
+ printf(
297
+ '<?xml-stylesheet type="text/xsl" href="%s"?>',
298
+ // phpcs:ignore, WordPress.Security.EscapeOutput
299
+ $this->get_expected_sitemap_endpoint_url( 'xsl-stylesheet' )
300
+ );
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Returns the opening tag for the sitemap urlset.
306
+ *
307
+ * @since 4.0.0
308
+ */
309
+ public function output_sitemap_urlset_open_tag() {
310
+
311
+ $schemas = [
312
+ 'xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9',
313
+ 'xmlns:xhtml' => 'http://www.w3.org/1999/xhtml',
314
+ 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
315
+ 'xsi:schemaLocation' => [
316
+ 'http://www.sitemaps.org/schemas/sitemap/0.9',
317
+ 'http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd',
318
+ ],
319
+ ];
320
+
321
+ /**
322
+ * @since 2.8.0
323
+ * @param array $schemas The schema list. URLs are expected to be escaped.
324
+ */
325
+ $schemas = (array) \apply_filters( 'the_seo_framework_sitemap_schemas', $schemas );
326
+
327
+ $urlset = '<urlset';
328
+ foreach ( $schemas as $type => $values ) {
329
+ $urlset .= ' ' . $type . '="';
330
+ if ( is_array( $values ) ) {
331
+ $urlset .= implode( ' ', $values );
332
+ } else {
333
+ $urlset .= $values;
334
+ }
335
+ $urlset .= '"';
336
+ }
337
+ $urlset .= '>';
338
+
339
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- Output is static from filter.
340
+ echo $urlset . "\n";
341
+ }
342
+
343
+ /**
344
+ * Outputs the closing tag for the sitemap urlset.
345
+ *
346
+ * @since 4.0.0
347
+ */
348
+ public function output_sitemap_urlset_close_tag() {
349
+ echo '</urlset>';
350
+ }
351
+
352
+ /**
353
+ * Returns the sitemap path prefix.
354
+ * Useful when the prefix path is non-standard, like notoriously in Polylang.
355
+ *
356
+ * @since 4.0.0
357
+ *
358
+ * @return string The path prefix.
359
+ */
360
+ private function get_sitemap_path_prefix() {
361
+ /**
362
+ * Ignore RFC2616 slashlessness by adding a slash;
363
+ * this makes life easier when trailing and testing the URL, as well.
364
+ *
365
+ * @since 4.0.0
366
+ * @param string $prefix The path prefix. Ideally appended with a slash.
367
+ * Recommended return value: "$prefix$custompath/"
368
+ */
369
+ return \apply_filters( 'the_seo_framework_sitemap_path_prefix', '/' );
370
+ }
371
+
372
+ /**
373
+ * Gets the sitemap ID based on the current request URI.
374
+ *
375
+ * @since 4.0.0
376
+ * @since 4.0.2 Can now parse Unicode-encoded URLs.
377
+ *
378
+ * @param string $raw_uri The raw request URI. Unsafe.
379
+ * @return string|false The endpoint ID on success, false on failure.
380
+ */
381
+ private function get_sitemap_id_from_uri( $raw_uri ) {
382
+
383
+ // The path+query where sitemaps are served.
384
+ $path_info = $this->get_sitemap_base_path_info();
385
+
386
+ // A regex which detects $sitemap_path at the beginning of a string.
387
+ $path_regex = '/^' . preg_quote( rawurldecode( $path_info['path'] ), '/' ) . '/ui';
388
+
389
+ // See if the base matches the endpoint. This is crucial for query-based endpoints.
390
+ if ( ! preg_match( $path_regex, $raw_uri ) ) return false;
391
+
392
+ $stripped_uri = preg_replace( $path_regex, '', rtrim( $raw_uri, '/' ) );
393
+
394
+ // Strip the base URI. If nothing's left, stop assimilating.
395
+ if ( ! $stripped_uri ) return false;
396
+
397
+ $sitemap_id = '';
398
+
399
+ // Loop over the sitemap endpoints, and see if it matches the stripped uri.
400
+ if ( $path_info['use_query_var'] ) {
401
+ foreach ( $this->get_sitemap_endpoint_list() as $_id => $_data ) {
402
+ $_regex = '/^' . preg_quote( $_id, '/' ) . '/i';
403
+ // Yes, we know. It's not really checking for standardized query-variables.
404
+ if ( preg_match( $_regex, $stripped_uri ) ) {
405
+ $sitemap_id = $_id;
406
+ break;
407
+ }
408
+ }
409
+ } else {
410
+ foreach ( $this->get_sitemap_endpoint_list() as $_id => $_data ) {
411
+ if ( preg_match( $_data['regex'], $stripped_uri ) ) {
412
+ $sitemap_id = $_id;
413
+ break;
414
+ }
415
+ }
416
+ }
417
+
418
+ return $sitemap_id ?: false;
419
+ }
420
+
421
+ /**
422
+ * Returns the base path information for the sitemap.
423
+ *
424
+ * @since 4.0.0
425
+ * @global \WP_Rewrite $wp_rewrite
426
+ *
427
+ * @return array : {
428
+ * string path : The sitemap base path, like subdirectories or translations.
429
+ * bool use_query_var : Whether to use the query var.
430
+ * }
431
+ */
432
+ private function get_sitemap_base_path_info() {
433
+ global $wp_rewrite;
434
+
435
+ $home_path = rtrim( parse_url( \get_home_url(), PHP_URL_PATH ), '/' );
436
+ $prefix = $this->get_sitemap_path_prefix();
437
+
438
+ $use_query_var = false;
439
+
440
+ if ( $wp_rewrite->using_index_permalinks() ) {
441
+ $path = "$home_path/index.php$prefix";
442
+ } elseif ( $wp_rewrite->using_permalinks() ) {
443
+ $path = "$home_path$prefix";
444
+ } else {
445
+ // Yes, we know. This is not really checking for standardized query-variables.
446
+ // It's straightforward and doesn't mess with the rest of the site, however.
447
+ $path = "$home_path$prefix?tsf-sitemap=";
448
+
449
+ $use_query_var = true;
450
+ }
451
+
452
+ return compact( 'path', 'use_query_var' );
453
+ }
454
+
455
+ /**
456
+ * Destroys unused $GLOBALS.
457
+ *
458
+ * This method is to be used prior to outputting sitemap.
459
+ *
460
+ * @since 2.6.0
461
+ * @since 2.8.0 Renamed from clean_up_globals().
462
+ * @since 4.0.0 1. Moved to \The_SEO_Framework\Bridges\Sitemap
463
+ * 2. Renamed from clean_up_globals_for_sitemap()
464
+ *
465
+ * @param bool $get_freed_memory Whether to return the freed memory in bytes.
466
+ * @return int $freed_memory
467
+ */
468
+ private function clean_up_globals( $get_freed_memory = false ) {
469
+
470
+ static $freed_memory = null;
471
+
472
+ if ( $get_freed_memory )
473
+ return $freed_memory;
474
+
475
+ $memory = memory_get_usage();
476
+
477
+ $remove = [
478
+ 'wp_filter' => [
479
+ 'wp_head',
480
+ 'admin_head',
481
+ 'the_content',
482
+ 'the_content_feed',
483
+ 'the_excerpt_rss',
484
+ 'wp_footer',
485
+ 'admin_footer',
486
+ ],
487
+ 'wp_registered_widgets',
488
+ 'wp_registered_sidebars',
489
+ 'wp_registered_widget_updates',
490
+ 'wp_registered_widget_controls',
491
+ '_wp_deprecated_widgets_callbacks',
492
+ 'posts',
493
+ ];
494
+
495
+ foreach ( $remove as $key => $value ) {
496
+ if ( is_array( $value ) ) {
497
+ foreach ( $value as $v )
498
+ unset( $GLOBALS[ $key ][ $v ] );
499
+ } else {
500
+ unset( $GLOBALS[ $value ] );
501
+ }
502
+ }
503
+
504
+ // This one requires to be an array for wp_texturize(). There's an API, let's use it:
505
+ \remove_all_shortcodes();
506
+
507
+ $freed_memory = $memory - memory_get_usage();
508
+ }
509
+ }
510
+
511
+ $_load_sitemap_class();
inc/classes/bridges/termsettings.class.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Bridges\TermSettings
4
+ * @subpackage The_SEO_Framework\Admin\Edit\Term
5
+ */
6
+
7
+ namespace The_SEO_Framework\Bridges;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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
+ * Prepares the Term Settings view interface.
30
+ *
31
+ * @since 4.0.0
32
+ * @access protected
33
+ * @internal
34
+ * @final Can't be extended.
35
+ */
36
+ final class TermSettings {
37
+ use \The_SEO_Framework\Traits\Enclose_Stray_Private;
38
+
39
+ /**
40
+ * Prepares the setting fields.
41
+ *
42
+ * @since 4.0.0
43
+ *
44
+ * @param \WP_Term $term Current taxonomy term object.
45
+ * @param string $taxonomy Current taxonomy slug.
46
+ */
47
+ public static function _prepare_setting_fields( $term, $taxonomy ) {
48
+ static::_output_setting_fields( $term, $taxonomy );
49
+ }
50
+
51
+ /**
52
+ * Outputs the term settings fields.
53
+ *
54
+ * @since 4.0.0
55
+ *
56
+ * @param \WP_Term $term Current taxonomy term object.
57
+ * @param string $taxonomy Current taxonomy slug.
58
+ */
59
+ public static function _output_setting_fields( $term, $taxonomy ) {
60
+ /**
61
+ * @since 2.9.0
62
+ */
63
+ \do_action( 'the_seo_framework_pre_tt_inpost_box' );
64
+ \the_seo_framework()->get_view( 'edit/seo-settings-tt', get_defined_vars() );
65
+ /**
66
+ * @since 2.9.0
67
+ */
68
+ \do_action( 'the_seo_framework_pro_tt_inpost_box' );
69
+ }
70
+ }
inc/classes/builders/images.class.php ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Builders\Images
4
+ * @subpackage The_SEO_Framework\Getters\Image
5
+ */
6
+
7
+ namespace The_SEO_Framework\Builders;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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 images.
30
+ *
31
+ * @since 4.0.0
32
+ */
33
+ final class Images {
34
+ use \The_SEO_Framework\Traits\Enclose_Core_Final;
35
+
36
+ /**
37
+ * The constructor. Or rather, the lack thereof.
38
+ *
39
+ * @since 4.0.0
40
+ */
41
+ private function __construct() { }
42
+
43
+ /**
44
+ * Generates image URLs and IDs from the attachment page entry.
45
+ *
46
+ * @since 4.0.0
47
+ * @generator
48
+ *
49
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
50
+ * Leave null to autodetermine query.
51
+ * @param string $size The size of the image to get.
52
+ * @yield array : {
53
+ * string url: The image URL location,
54
+ * int id: The image ID,
55
+ * }
56
+ */
57
+ public static function get_attachment_image_details( $args = null, $size = 'full' ) {
58
+
59
+ $id = isset( $args['id'] ) ? $args['id'] : \the_seo_framework()->get_the_real_ID();
60
+
61
+ if ( $id ) {
62
+ yield [
63
+ 'url' => \wp_get_attachment_image_url( $id, $size ) ?: '',
64
+ 'id' => $id,
65
+ ];
66
+ } else {
67
+ yield [
68
+ 'url' => '',
69
+ 'id' => 0,
70
+ ];
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Generates image URLs and IDs from the featured image input.
76
+ *
77
+ * @since 4.0.0
78
+ * @generator
79
+ *
80
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
81
+ * Leave null to autodetermine query.
82
+ * @param string $size The size of the image to get.
83
+ * @yield array : {
84
+ * string url: The image URL location,
85
+ * int id: The image ID,
86
+ * }
87
+ */
88
+ public static function get_featured_image_details( $args = null, $size = 'full' ) {
89
+
90
+ $post_id = isset( $args['id'] ) ? $args['id'] : \the_seo_framework()->get_the_real_ID();
91
+ $id = \get_post_thumbnail_id( $post_id );
92
+
93
+ if ( $id ) {
94
+ yield [
95
+ 'url' => \wp_get_attachment_image_url( $id, $size ) ?: '',
96
+ 'id' => $id,
97
+ ];
98
+ } else {
99
+ yield [
100
+ 'url' => '',
101
+ 'id' => 0,
102
+ ];
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Generates image URLs and IDs from the content.
108
+ *
109
+ * @since 4.0.0
110
+ * @generator
111
+ * TODO consider matching these images with wp-content/uploads items via database calls, which is heavy...
112
+ * Combine query, instead of using WP API? Only do that for the first image, instead?
113
+ *
114
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
115
+ * Leave null to autodetermine query.
116
+ * @param string $size The size of the image to get.
117
+ * @yield array : {
118
+ * string url: The image URL location,
119
+ * int id: The image ID,
120
+ * }
121
+ */
122
+ public static function get_content_image_details( $args = null, $size = 'full' ) {
123
+
124
+ $tsf = \the_seo_framework();
125
+
126
+ if ( null === $args ) {
127
+ if ( $tsf->is_singular() ) {
128
+ $content = $tsf->get_post_content();
129
+ }
130
+ } else {
131
+ if ( $args['taxonomy'] ) {
132
+ $content = '';
133
+ } else {
134
+ $content = $tsf->get_post_content( $args['id'] );
135
+ }
136
+ }
137
+
138
+ $matches = [];
139
+
140
+ // strlen( '<img src=a>' ) === 11; yes, that's a valid self-closing tag with a relative source.
141
+ if ( strlen( $content ) > 10 && false !== stripos( $content, '<img' ) ) {
142
+ preg_match_all(
143
+ '/<img[^>]+src=(\"|\')?([^\"\'>\s]+)\1?.*?>/mi',
144
+ $content,
145
+ $matches,
146
+ PREG_SET_ORDER
147
+ );
148
+ }
149
+
150
+ if ( $matches ) {
151
+ foreach ( $matches as $match ) {
152
+ // Assume every URL to be correct? Yes. WordPress assumes that too.
153
+ yield [
154
+ 'url' => $match[2] ?: '',
155
+ 'id' => 0,
156
+ ];
157
+ }
158
+ } else {
159
+ yield [
160
+ 'url' => '',
161
+ 'id' => 0,
162
+ ];
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Generates image URLs and IDs from the fallback image options.
168
+ *
169
+ * @since 4.0.0
170
+ * @generator
171
+ *
172
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
173
+ * Leave null to autodetermine query.
174
+ * @param string $size The size of the image to get.
175
+ * @yield array : {
176
+ * string url: The image URL location,
177
+ * int id: The image ID,
178
+ * }
179
+ */
180
+ public static function get_fallback_image_details( $args = null, $size = 'full' ) {
181
+
182
+ $tsf = \the_seo_framework();
183
+
184
+ yield [
185
+ 'url' => $tsf->get_option( 'social_image_fb_url' ) ?: '',
186
+ 'id' => $tsf->get_option( 'social_image_fb_id' ) ?: 0,
187
+ ];
188
+ }
189
+
190
+ /**
191
+ * Generates image URLs and IDs from the theme header defaults or options.
192
+ *
193
+ * N.B. This output may be randomized.
194
+ *
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 : {
202
+ * string url: The image URL location,
203
+ * int id: The image ID,
204
+ * }
205
+ */
206
+ public static function get_theme_header_image_details( $args = null, $size = 'full' ) {
207
+
208
+ $header = \get_custom_header();
209
+
210
+ if ( empty( $header->attachment_id ) ) { // This property isn't returned by default.
211
+ yield [
212
+ 'url' => $header->url ?: '',
213
+ 'id' => 0,
214
+ ];
215
+ } else {
216
+ yield [
217
+ 'url' => \wp_get_attachment_image_url( $header->attachment_id, $size ) ?: '',
218
+ 'id' => $header->attachment_id,
219
+ ];
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Generates image URLs and IDs from the logo modification.
225
+ *
226
+ * @since 4.0.0
227
+ * @generator
228
+ *
229
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
230
+ * Leave null to autodetermine query.
231
+ * @param string $size The size of the image to get.
232
+ * @yield array : {
233
+ * string url: The image URL location,
234
+ * int id: The image ID,
235
+ * }
236
+ */
237
+ public static function get_site_logo_image_details( $args = null, $size = 'full' ) {
238
+
239
+ $id = \get_theme_mod( 'custom_logo' );
240
+
241
+ if ( $id ) {
242
+ yield [
243
+ 'url' => \wp_get_attachment_image_url( $id, $size ) ?: '',
244
+ 'id' => $id,
245
+ ];
246
+ } else {
247
+ yield [
248
+ 'url' => '',
249
+ 'id' => 0,
250
+ ];
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Generates image URLs and IDs from site icon options.
256
+ *
257
+ * @since 4.0.0
258
+ * @generator
259
+ *
260
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
261
+ * Leave null to autodetermine query.
262
+ * @param string $size The size of the image to get.
263
+ * @yield array : {
264
+ * string url: The image URL location,
265
+ * int id: The image ID,
266
+ * }
267
+ */
268
+ public static function get_site_icon_image_details( $args = null, $size = 'full' ) {
269
+
270
+ $id = \get_option( 'site_icon' );
271
+
272
+ if ( $id ) {
273
+ yield [
274
+ 'url' => \wp_get_attachment_image_url( $id, $size ) ?: '',
275
+ 'id' => $id,
276
+ ];
277
+ } else {
278
+ yield [
279
+ 'url' => '',
280
+ 'id' => 0,
281
+ ];
282
+ }
283
+ }
284
+ }
inc/classes/builders/index.php ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Home wasn't built in a day.
4
+ *
5
+ * - Jane "Sherwood" Ace[-Epstein]
6
+ */
inc/classes/builders/scripts.class.php CHANGED
@@ -1,8 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes\Builders
4
- * @subpackage The_SEO_Framework\Builders
5
  */
 
6
  namespace The_SEO_Framework\Builders;
7
 
8
  /**
@@ -27,6 +28,7 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
27
  /**
28
  * Sets up class loader as file is loaded.
29
  * This is done asynchronously, because static calls are handled prior and after.
 
30
  * @see EOF. Because of the autoloader and (future) trait calling, we can't do it before the class is read.
31
  * @link https://bugs.php.net/bug.php?id=75771
32
  */
@@ -35,48 +37,56 @@ $_load_scripts_class = function() {
35
  };
36
 
37
  /**
38
- * Registers and outputs inpost GUI scripts. Auto-invokes everything the moment
39
  * this file is required.
40
  * Relies on \WP_Dependencies to prevent duplicate loading, and autoloading.
41
  *
42
  * This handles admin-ONLY scripts for now.
43
  *
44
  * @since 3.1.0
45
- * @see the_seo_framework()->Scripts()
46
  * @see \WP_Styles
47
  * @see \WP_Scripts
48
  * @see \WP_Dependencies
 
49
  * @access private
50
- * Use `the_seo_framework()->Scripts()` instead.
51
  * @final Can't be extended.
52
  */
53
  final class Scripts {
 
54
 
55
  /**
56
  * Codes to maintain the internal state of the scripts. This state might not reflect
57
  * the actual load state. See \WP_Dependencies instead.
 
58
  * @since 3.1.0
59
  * @internal
60
- * @param int <bit 1> REGISTERED
61
- * @param int <bit 10> LOADED (enqueued)
62
  */
63
- const REGISTERED = 0b1;
64
  const LOADED = 0b10;
65
 
66
  /**
67
  * @since 3.1.0
68
- * @param array $scripts The registered scripts.
69
- * @param array $templates The registered templates.
70
- * @param array $queue The queued scripts state.
 
 
 
 
71
  */
72
- private static $scripts = [];
73
  private static $templates = [];
74
- private static $queue = [];
75
 
76
  /**
77
- * The internal singleton object holder.
78
  * @since 3.1.0
79
- * @param The_SEO_Framework\Builders\Scripts $instance The instance.
 
 
 
 
 
 
80
  */
81
  private static $instance;
82
 
@@ -84,7 +94,7 @@ final class Scripts {
84
  * @since 3.1.0
85
  * @since 3.2.2 Is now a private variable.
86
  * @see static::verify()
87
- * @param string|null $include_secret The inclusion secret generated on tab load.
88
  */
89
  private static $include_secret;
90
 
@@ -105,6 +115,7 @@ final class Scripts {
105
  *
106
  * @since 3.1.0
107
  * @access private
 
108
  * @internal
109
  */
110
  public function __construct() {
@@ -114,20 +125,47 @@ final class Scripts {
114
 
115
  static::$instance = &$this;
116
 
 
 
 
117
  \add_action( 'admin_enqueue_scripts', [ $this, '_prepare_admin_scripts' ], 1 );
118
  \add_action( 'admin_footer', [ $this, '_output_templates' ], 999 );
119
  }
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  /**
122
  * Prepares scripts for output on post edit screens.
123
  *
124
  * @since 3.1.0
125
  * @access private
126
  * @internal
127
- *
128
- * @param string $hook The current admin hook.
129
  */
130
- public function _prepare_admin_scripts( $hook = '' ) {
131
  $this->forward_known_scripts();
132
  $this->autoload_known_scripts();
133
  }
@@ -296,7 +334,7 @@ final class Scripts {
296
  * @see static::enqueue_known_script();
297
  * @augments static::$queue
298
  * @uses $this->generate_file_url()
299
- * @uses $this->get_inline_css()
300
  * @uses $this->register_template()
301
  *
302
  * @param array $s The script.
@@ -310,7 +348,7 @@ final class Scripts {
310
  case 'css':
311
  \wp_register_style( $s['id'], $instance->generate_file_url( $s, 'css' ), $s['deps'], $s['ver'], 'all' );
312
  isset( $s['inline'] )
313
- and \wp_add_inline_style( $s['id'], $instance->get_inline_css( $s['inline'] ) );
314
  $registered = true;
315
  break;
316
  case 'js':
@@ -319,13 +357,15 @@ final class Scripts {
319
  and \wp_localize_script( $s['id'], $s['l10n']['name'], $s['l10n']['data'] );
320
  isset( $s['tmpl'] )
321
  and $instance->register_template( $s['id'], $s['tmpl'] );
 
 
322
  $registered = true;
323
  break;
324
  }
325
  if ( $registered ) {
326
  isset( static::$queue[ $s['type'] ][ $s['id'] ] )
327
  and static::$queue[ $s['type'] ][ $s['id'] ] |= static::REGISTERED
328
- or static::$queue[ $s['type'] ][ $s['id'] ] = static::REGISTERED; // Precision alignment ok.
329
  }
330
  }
331
 
@@ -356,7 +396,7 @@ final class Scripts {
356
  if ( $loaded ) {
357
  isset( static::$queue[ $type ][ $id ] )
358
  and static::$queue[ $type ][ $id ] |= static::LOADED
359
- or static::$queue[ $type ][ $id ] = static::LOADED; // Precision alignment ok.
360
  }
361
  }
362
 
@@ -395,11 +435,12 @@ final class Scripts {
395
  * - {{$color_accent}}
396
  *
397
  * @since 3.1.0
 
398
  *
399
- * @param array $colors The color CSS.
400
- * @return array $css
401
  */
402
- private function get_inline_css( array $styles ) {
403
 
404
  $out = '';
405
  foreach ( $styles as $selector => $css ) {
@@ -409,6 +450,25 @@ final class Scripts {
409
  return $out;
410
  }
411
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  /**
413
  * Converts color CSS.
414
  *
@@ -416,7 +476,7 @@ final class Scripts {
416
  * @staticvar array $c_ck Color keys.
417
  * @staticvar array $c_cv Color values.
418
  *
419
- * @param array $css
420
  * @return array $css
421
  */
422
  private function convert_color_css( array $css ) {
@@ -430,7 +490,7 @@ final class Scripts {
430
  $tsf = \the_seo_framework();
431
 
432
  if (
433
- ! isset( $_colors[ $_scheme ]->colors ) // Precision alignment OK.
434
  || ! is_array( $_colors[ $_scheme ]->colors )
435
  || count( $_colors[ $_scheme ]->colors ) < 4
436
  ) {
@@ -460,7 +520,7 @@ final class Scripts {
460
  '{{$bg_accent}}' => $_bg_accent,
461
  '{{$rel_bg_accent}}' => $_rel_bg_accent,
462
  '{{$color}}' => $_color,
463
- '{{$rel_color}}' => $_color,
464
  '{{$color_accent}}' => $_color_accent,
465
  '{{$rel_color_accent}}' => $_rel_color_accent,
466
  ];
@@ -479,8 +539,8 @@ final class Scripts {
479
  *
480
  * @since 3.1.0
481
  *
482
- * @param string $id, the related script handle/ID.
483
- * @param array $templates, associative-&-singul-, or sequential-&-multi-dimensional : {
484
  * 'file' => string $file. The full file location,
485
  * 'args' => array $args. Optional,
486
  * }
@@ -527,6 +587,7 @@ final class Scripts {
527
  *
528
  * There's a secret key generated on each tab load. This key can be accessed
529
  * in the view through `$_secret`, and be sent back to this class.
 
530
  * @see static::verify( $secret )
531
  *
532
  * @since 3.1.0
@@ -542,7 +603,8 @@ final class Scripts {
542
  $$_key = $_val;
543
  unset( $_key, $_val, $args );
544
 
545
- //= Prevent private includes hijacking.
 
546
  static::$include_secret = $_secret = mt_rand() . uniqid( '', true );
547
  include $file;
548
  static::$include_secret = null;
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Builders\Scripts
4
+ * @subpackage The_SEO_Framework\Scripts
5
  */
6
+
7
  namespace The_SEO_Framework\Builders;
8
 
9
  /**
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
  */
37
  };
38
 
39
  /**
40
+ * Registers and outputs admin GUI scripts. Auto-invokes everything the moment
41
  * this file is required.
42
  * Relies on \WP_Dependencies to prevent duplicate loading, and autoloading.
43
  *
44
  * This handles admin-ONLY scripts for now.
45
  *
46
  * @since 3.1.0
 
47
  * @see \WP_Styles
48
  * @see \WP_Scripts
49
  * @see \WP_Dependencies
50
+ * @see \The_SEO_Framework\Bridges\Scripts
51
  * @access private
 
52
  * @final Can't be extended.
53
  */
54
  final class Scripts {
55
+ use \The_SEO_Framework\Traits\Enclose_Stray_Private;
56
 
57
  /**
58
  * Codes to maintain the internal state of the scripts. This state might not reflect
59
  * the actual load state. See \WP_Dependencies instead.
60
+ *
61
  * @since 3.1.0
62
  * @internal
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
71
+ * @var array $scripts The registered scripts.
72
+ */
73
+ private static $scripts = [];
74
+
75
+ /**
76
+ * @since 3.1.0
77
+ * @var array $templates The registered templates.
78
  */
 
79
  private static $templates = [];
 
80
 
81
  /**
 
82
  * @since 3.1.0
83
+ * @var array $queue The queued scripts state.
84
+ */
85
+ private static $queue = [];
86
+
87
+ /**
88
+ * @since 3.1.0
89
+ * @var \The_SEO_Framework\Builders\Scripts $instance The instance.
90
  */
91
  private static $instance;
92
 
94
  * @since 3.1.0
95
  * @since 3.2.2 Is now a private variable.
96
  * @see static::verify()
97
+ * @var string|null $include_secret The inclusion secret generated on tab load.
98
  */
99
  private static $include_secret;
100
 
115
  *
116
  * @since 3.1.0
117
  * @access private
118
+ * @staticvar int $count Enforces singleton.
119
  * @internal
120
  */
121
  public function __construct() {
125
 
126
  static::$instance = &$this;
127
 
128
+ \add_filter( 'admin_body_class', [ $this, '_add_body_class' ] );
129
+ \add_action( 'in_admin_header', [ $this, '_print_tsfjs_script' ] );
130
+
131
  \add_action( 'admin_enqueue_scripts', [ $this, '_prepare_admin_scripts' ], 1 );
132
  \add_action( 'admin_footer', [ $this, '_output_templates' ], 999 );
133
  }
134
 
135
+ /**
136
+ * Adds admin-body classes.
137
+ *
138
+ * @since 4.0.0
139
+ * @access private
140
+ * @internal
141
+ *
142
+ * @param string $classes Space-separated list of CSS classes.
143
+ * @return string
144
+ */
145
+ public function _add_body_class( $classes ) {
146
+ // Add spaces at both sides, because who knows what others do.
147
+ return ' tsf-no-js ' . $classes;
148
+ }
149
+
150
+ /**
151
+ * Prints the TSF no-js transform script, using ES2015 (ECMA-262).
152
+ *
153
+ * @since 4.0.0
154
+ * @access private
155
+ * @internal
156
+ */
157
+ public function _print_tsfjs_script() {
158
+ echo "<script>(()=>{document.body.classList.replace('tsf-no-js','tsf-js');const a=0;})()</script>";
159
+ }
160
+
161
  /**
162
  * Prepares scripts for output on post edit screens.
163
  *
164
  * @since 3.1.0
165
  * @access private
166
  * @internal
 
 
167
  */
168
+ public function _prepare_admin_scripts() {
169
  $this->forward_known_scripts();
170
  $this->autoload_known_scripts();
171
  }
334
  * @see static::enqueue_known_script();
335
  * @augments static::$queue
336
  * @uses $this->generate_file_url()
337
+ * @uses $this->create_inline_css()
338
  * @uses $this->register_template()
339
  *
340
  * @param array $s The script.
348
  case 'css':
349
  \wp_register_style( $s['id'], $instance->generate_file_url( $s, 'css' ), $s['deps'], $s['ver'], 'all' );
350
  isset( $s['inline'] )
351
+ and \wp_add_inline_style( $s['id'], $instance->create_inline_css( $s['inline'] ) );
352
  $registered = true;
353
  break;
354
  case 'js':
357
  and \wp_localize_script( $s['id'], $s['l10n']['name'], $s['l10n']['data'] );
358
  isset( $s['tmpl'] )
359
  and $instance->register_template( $s['id'], $s['tmpl'] );
360
+ isset( $s['inline'] )
361
+ and \wp_add_inline_script( $s['id'], $instance->create_inline_js( $s['inline'] ) );
362
  $registered = true;
363
  break;
364
  }
365
  if ( $registered ) {
366
  isset( static::$queue[ $s['type'] ][ $s['id'] ] )
367
  and static::$queue[ $s['type'] ][ $s['id'] ] |= static::REGISTERED
368
+ or static::$queue[ $s['type'] ][ $s['id'] ] = static::REGISTERED; // phpcs:ignore, WordPress.WhiteSpace
369
  }
370
  }
371
 
396
  if ( $loaded ) {
397
  isset( static::$queue[ $type ][ $id ] )
398
  and static::$queue[ $type ][ $id ] |= static::LOADED
399
+ or static::$queue[ $type ][ $id ] = static::LOADED; // phpcs:ignore, WordPress.WhiteSpace
400
  }
401
  }
402
 
435
  * - {{$color_accent}}
436
  *
437
  * @since 3.1.0
438
+ * @uses $this->convert_color_css()
439
  *
440
+ * @param array $styles The styles to add.
441
+ * @return string
442
  */
443
+ private function create_inline_css( array $styles ) {
444
 
445
  $out = '';
446
  foreach ( $styles as $selector => $css ) {
450
  return $out;
451
  }
452
 
453
+
454
+ /**
455
+ * Concatenates inline JS.
456
+ *
457
+ * @since 4.0.0
458
+ *
459
+ * @param array $scripts The scripts to add.
460
+ * @return string
461
+ */
462
+ private function create_inline_js( array $scripts ) {
463
+
464
+ $out = '';
465
+ foreach ( $scripts as $script ) {
466
+ $out .= ";$script";
467
+ }
468
+
469
+ return $out;
470
+ }
471
+
472
  /**
473
  * Converts color CSS.
474
  *
476
  * @staticvar array $c_ck Color keys.
477
  * @staticvar array $c_cv Color values.
478
  *
479
+ * @param array $css The CSS to convert.
480
  * @return array $css
481
  */
482
  private function convert_color_css( array $css ) {
490
  $tsf = \the_seo_framework();
491
 
492
  if (
493
+ ! isset( $_colors[ $_scheme ]->colors ) // phpcs:ignore, WordPress.WhiteSpace
494
  || ! is_array( $_colors[ $_scheme ]->colors )
495
  || count( $_colors[ $_scheme ]->colors ) < 4
496
  ) {
520
  '{{$bg_accent}}' => $_bg_accent,
521
  '{{$rel_bg_accent}}' => $_rel_bg_accent,
522
  '{{$color}}' => $_color,
523
+ '{{$rel_color}}' => $_rel_color,
524
  '{{$color_accent}}' => $_color_accent,
525
  '{{$rel_color_accent}}' => $_rel_color_accent,
526
  ];
539
  *
540
  * @since 3.1.0
541
  *
542
+ * @param string $id The related script handle/ID.
543
+ * @param array $templates Associative-&-singul-, or sequential-&-multi-dimensional : {
544
  * 'file' => string $file. The full file location,
545
  * 'args' => array $args. Optional,
546
  * }
587
  *
588
  * There's a secret key generated on each tab load. This key can be accessed
589
  * in the view through `$_secret`, and be sent back to this class.
590
+ *
591
  * @see static::verify( $secret )
592
  *
593
  * @since 3.1.0
603
  $$_key = $_val;
604
  unset( $_key, $_val, $args );
605
 
606
+ //= Prevents private-includes hijacking.
607
+ // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis -- Read the include?
608
  static::$include_secret = $_secret = mt_rand() . uniqid( '', true );
609
  include $file;
610
  static::$include_secret = null;
inc/classes/builders/seobar-page.class.php ADDED
@@ -0,0 +1,986 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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 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
42
+ * @abstract
43
+ * @var array All known tests.
44
+ */
45
+ public static $tests = [ 'title', 'description', 'indexing', 'following', 'archiving', 'redirect' ];
46
+
47
+ /**
48
+ * Primes the cache.
49
+ *
50
+ * @since 4.0.0
51
+ * @abstract
52
+ */
53
+ protected function prime_cache() {
54
+ static::get_cache( 'general/i18n/inputguidelines' )
55
+ or static::set_cache(
56
+ 'general/i18n/inputguidelines',
57
+ static::$tsf->get_input_guidelines_i18n()
58
+ );
59
+
60
+ static::get_cache( 'general/detect/robotsglobal' )
61
+ or static::set_cache(
62
+ 'general/detect/robotsglobal',
63
+ [
64
+ 'hasrobotstxt' => static::$tsf->has_robots_txt(),
65
+ 'blogpublic' => static::$tsf->is_blog_public(),
66
+ 'site' => [
67
+ 'noindex' => static::$tsf->get_option( 'site_noindex' ),
68
+ 'nofollow' => static::$tsf->get_option( 'site_nofollow' ),
69
+ 'noarchive' => static::$tsf->get_option( 'site_noarchive' ),
70
+ ],
71
+ 'posttype' => [
72
+ 'noindex' => static::$tsf->get_option( static::$tsf->get_robots_post_type_option_id( 'noindex' ) ),
73
+ 'nofollow' => static::$tsf->get_option( static::$tsf->get_robots_post_type_option_id( 'nofollow' ) ),
74
+ 'noarchive' => static::$tsf->get_option( static::$tsf->get_robots_post_type_option_id( 'noarchive' ) ),
75
+ ],
76
+ ]
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Primes the current query cache.
82
+ *
83
+ * @since 4.0.0
84
+ * @abstract
85
+ *
86
+ * @param array $query_cache The current query cache. Passed by reference.
87
+ */
88
+ protected function prime_query_cache( array &$query_cache = [] ) {
89
+ $query_cache = [
90
+ 'post' => \get_post( static::$query['id'] ),
91
+ 'meta' => static::$tsf->get_post_meta( static::$query['id'], true ), // Use TSF cache--TSF initializes it anyway.
92
+ 'states' => [
93
+ 'ishome' => static::$tsf->is_real_front_page_by_id( static::$query['id'] ),
94
+ 'locale' => \get_locale(),
95
+ 'isprotected' => static::$tsf->is_protected( static::$query['id'] ),
96
+ 'isdraft' => static::$tsf->is_draft( static::$query['id'] ),
97
+ 'robotsmeta' => array_merge(
98
+ [
99
+ 'noindex' => false,
100
+ 'nofollow' => false,
101
+ 'noarchive' => false,
102
+ ],
103
+ static::$tsf->robots_meta( [
104
+ 'id' => static::$query['id'],
105
+ ] )
106
+ ),
107
+ ],
108
+ ];
109
+ }
110
+
111
+ /**
112
+ * Tests for blocking redirection.
113
+ *
114
+ * @since 4.0.0
115
+ * @abstract
116
+ *
117
+ * @return bool True if there's a blocking redirect, false otherwise.
118
+ */
119
+ protected function has_blocking_redirect() {
120
+ return ! empty( $this->query_cache['meta']['redirect'] );
121
+ }
122
+
123
+ /**
124
+ * Runs title tests.
125
+ *
126
+ * @since 4.0.0
127
+ *
128
+ * @return array $item : {
129
+ * string $symbol : The displayed symbol that identifies your bar.
130
+ * string $title : The title of the assessment.
131
+ * int $status : Power of two. See \The_SEO_Framework\Interpreters\SeoBar's class constants.
132
+ * string $reason : The final assessment: The reason for the $status. The latest state-changing reason is used.
133
+ * string $assess : The assessments on why the reason is set. Keep it short and concise!
134
+ * Does not accept HTML for performant ARIA support.
135
+ * }
136
+ */
137
+ protected function test_title() {
138
+
139
+ $cache = static::get_cache( 'page/title/defaults' ) ?: static::set_cache(
140
+ 'page/title/defaults',
141
+ [
142
+ 'params' => [
143
+ 'untitled' => static::$tsf->get_static_untitled_title(),
144
+ 'blogname_quoted' => preg_quote( static::$tsf->get_blogname(), '/' ),
145
+ /* translators: 1 = An assessment, 2 = Disclaimer, e.g. "take it with a grain of salt" */
146
+ 'disclaim' => \__( '%1$s (%2$s)', 'autodescription' ),
147
+ 'estimated' => \__( 'Estimated from the number of characters found. The pixel counter asserts the true length.', 'autodescription' ),
148
+ ],
149
+ 'assess' => [
150
+ 'empty' => \__( 'No title could be fetched.', 'autodescription' ),
151
+ 'untitled' => \__( 'No title could be fetched, "Untitled" is used instead.', 'autodescription' ), // TODO use [params][untitled]?
152
+ 'protected' => \__( 'A page protection state is added which increases the length.', 'autodescription' ),
153
+ 'branding' => [
154
+ 'not' => \__( "It's not branded. Search engines may ignore your title.", 'autodescription' ),
155
+ 'manual' => \__( "It's manually branded.", 'autodescription' ),
156
+ 'automatic' => \__( "It's automatically branded.", 'autodescription' ),
157
+ ],
158
+ 'duplicated' => \__( 'The blog name is found multiple times.', 'autodescription' ),
159
+ ],
160
+ 'reason' => [
161
+ 'incomplete' => \__( 'Incomplete.', 'autodescription' ),
162
+ 'duplicated' => \__( 'The branding is duplicated.', 'autodescription' ),
163
+ 'notbranded' => \__( 'Not branded.', 'autodescription' ),
164
+ ],
165
+ 'defaults' => [
166
+ 'generated' => [
167
+ 'symbol' => \_x( 'TG', 'Title Generated', 'autodescription' ),
168
+ 'title' => \__( 'Title, generated', 'autodescription' ),
169
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
170
+ 'reason' => \__( 'Automatically generated.', 'autodescription' ),
171
+ 'assess' => [
172
+ 'base' => \__( "It's built using the page title.", 'autodescription' ),
173
+ ],
174
+ ],
175
+ 'custom' => [
176
+ 'symbol' => \_x( 'T', 'Title', 'autodescription' ),
177
+ 'title' => \__( 'Title', 'autodescription' ),
178
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
179
+ 'reason' => \__( 'Obtained from page SEO meta input.', 'autodescription' ),
180
+ 'assess' => [
181
+ 'base' => \__( "It's built from page SEO meta input.", 'autodescription' ),
182
+ ],
183
+ ],
184
+ ],
185
+ ]
186
+ );
187
+
188
+ $title_args = [
189
+ 'id' => static::$query['id'],
190
+ ];
191
+
192
+ // TODO instead of getting values from the options API, why don't we store the parameters and allow them to be modified?
193
+ // This way, we can implement AJAX SEO bar items...
194
+ $title_part = static::$tsf->get_filtered_raw_custom_field_title( $title_args, false );
195
+
196
+ if ( strlen( $title_part ) ) {
197
+ $item = $cache['defaults']['custom'];
198
+
199
+ if ( $this->query_cache['states']['ishome'] ) {
200
+ // Don't use cache here, only one page can have this state.
201
+ if ( static::$tsf->get_option( 'homepage_title' ) ) {
202
+ $item['assess']['homepage'] = \__( 'The title inputted at the SEO Settings screen is used.', 'autodescription' );
203
+ } else {
204
+ $item['assess']['homepage'] = \__( 'The title inputted at the Edit Page screen is used.', 'autodescription' );
205
+ }
206
+ }
207
+ } else {
208
+ $item = $cache['defaults']['generated'];
209
+
210
+ if ( $this->query_cache['states']['ishome'] ) {
211
+ // Don't use cache here, only one page can have this state.
212
+ $item['assess']['base'] = \__( 'The title is built from the blog name.', 'autodescription' );
213
+ }
214
+
215
+ $title_part = static::$tsf->get_filtered_raw_generated_title( $title_args, false );
216
+ }
217
+
218
+ if ( ! $title_part ) {
219
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
220
+ $item['reason'] = $cache['reason']['incomplete'];
221
+ $item['assess']['empty'] = $cache['assess']['empty'];
222
+
223
+ // Further assessments must be made later. Halt assertion here to prevent confusion.
224
+ return $item;
225
+ } elseif ( $title_part === $cache['params']['untitled'] ) {
226
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
227
+ $item['reason'] = $cache['reason']['incomplete'];
228
+ $item['assess']['untitled'] = $cache['assess']['untitled'];
229
+
230
+ // Further assessments must be made later. Halt assertion here to prevent confusion.
231
+ return $item;
232
+ }
233
+
234
+ $title = $title_part;
235
+
236
+ // Don't use cache, as this can be filtered.
237
+ if ( static::$tsf->use_title_protection( $title_args ) ) {
238
+ $_title_before = $title;
239
+ static::$tsf->merge_title_protection( $title, $title_args );
240
+ if ( $title !== $_title_before )
241
+ $item['assess']['protected'] = $cache['assess']['protected'];
242
+ }
243
+
244
+ if ( static::$tsf->use_title_branding( $title_args ) ) {
245
+ $_title_before = $title;
246
+ static::$tsf->merge_title_branding( $title, $title_args );
247
+
248
+ // Absence assertion is done after this.
249
+ if ( $title === $_title_before ) {
250
+ $item['assess']['branding'] = $cache['assess']['branding']['manual'];
251
+ } else {
252
+ // This is true unless it's the home page and the user passed the blog name exactly.
253
+ $item['assess']['branding'] = $cache['assess']['branding']['automatic'];
254
+ }
255
+ } else {
256
+ // Absence assertion is done after this.
257
+ if ( $this->query_cache['states']['ishome'] ) {
258
+ // This is true unless it's the home page and the user passed the blog name exactly.
259
+ $item['assess']['branding'] = $cache['assess']['branding']['automatic'];
260
+ } else {
261
+ $item['assess']['branding'] = $cache['assess']['branding']['manual'];
262
+ }
263
+ }
264
+
265
+ $brand_count =
266
+ strlen( $cache['params']['blogname_quoted'] )
267
+ ? preg_match_all(
268
+ "/{$cache['params']['blogname_quoted']}/ui",
269
+ $title,
270
+ $matches
271
+ )
272
+ : 0;
273
+
274
+ if ( ! $brand_count ) {
275
+ // Override branding state.
276
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
277
+ $item['reason'] = $cache['reason']['notbranded'];
278
+ $item['assess']['branding'] = $cache['assess']['branding']['not'];
279
+ } elseif ( $brand_count > 1 ) {
280
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
281
+ $item['reason'] = $cache['reason']['duplicated'];
282
+ $item['assess']['duplicated'] = $cache['assess']['duplicated'];
283
+
284
+ // Further assessments must be made later. Halt assertion here to prevent confusion.
285
+ return $item;
286
+ }
287
+
288
+ $title_len = mb_strlen(
289
+ html_entity_decode(
290
+ \wp_specialchars_decode( static::$tsf->s_title_raw( $title ), ENT_QUOTES ),
291
+ ENT_NOQUOTES
292
+ )
293
+ );
294
+
295
+ $guidelines = static::$tsf->get_input_guidelines( $this->query_cache['states']['locale'] )['title']['search']['chars'];
296
+ $guidelines_i18n = static::get_cache( 'general/i18n/inputguidelines' );
297
+
298
+ if ( $title_len < $guidelines['lower'] ) {
299
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
300
+ $item['reason'] = $guidelines_i18n['shortdot']['farTooShort'];
301
+ $length_i18n = $guidelines_i18n['long']['farTooShort'];
302
+ } elseif ( $title_len < $guidelines['goodLower'] ) {
303
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
304
+ $item['reason'] = $guidelines_i18n['shortdot']['tooShort'];
305
+ $length_i18n = $guidelines_i18n['long']['tooShort'];
306
+ } elseif ( $title_len > $guidelines['upper'] ) {
307
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
308
+ $item['reason'] = $guidelines_i18n['shortdot']['farTooLong'];
309
+ $length_i18n = $guidelines_i18n['long']['farTooLong'];
310
+ } elseif ( $title_len > $guidelines['goodUpper'] ) {
311
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
312
+ $item['reason'] = $guidelines_i18n['shortdot']['tooLong'];
313
+ $length_i18n = $guidelines_i18n['long']['tooLong'];
314
+ } else {
315
+ // Use unaltered reason and status.
316
+ $length_i18n = $guidelines_i18n['long']['good'];
317
+ }
318
+
319
+ $item['assess']['length'] = sprintf(
320
+ $cache['params']['disclaim'],
321
+ $length_i18n,
322
+ $cache['params']['estimated']
323
+ );
324
+
325
+ return $item;
326
+ }
327
+
328
+ /**
329
+ * Runs title tests.
330
+ *
331
+ * @since 4.0.0
332
+ * @see test_title() for return value.
333
+ *
334
+ * @return array $item
335
+ */
336
+ protected function test_description() {
337
+
338
+ $cache = static::get_cache( 'page/description/defaults' ) ?: static::set_cache(
339
+ 'page/description/defaults',
340
+ [
341
+ 'params' => [
342
+ /* translators: 1 = An assessment, 2 = Disclaimer, e.g. "take it with a grain of salt" */
343
+ 'disclaim' => \__( '%1$s (%2$s)', 'autodescription' ),
344
+ 'estimated' => \__( 'Estimated from the number of characters found. The pixel counter asserts the true length.', 'autodescription' ),
345
+ /**
346
+ * @since 2.6.0
347
+ * @param int $dupe_short The minimum stringlength of words to find as dupes.
348
+ */
349
+ 'dupe_short' => (int) \apply_filters( 'the_seo_framework_bother_me_desc_length', 3 ),
350
+ ],
351
+ 'assess' => [
352
+ 'empty' => \__( 'There is no usable content, so no description could be generated.', 'autodescription' ),
353
+ 'builder' => \__( 'A foreign page builder is used, so no description is generated.', 'autodescription' ),
354
+ 'protected' => \__( 'The page is protected, so no description is generated.', 'autodescription' ),
355
+ 'excerpt' => \__( "It's built using the excerpt field.", 'autodescription' ),
356
+ /* translators: %s = list of duplicated words */
357
+ 'dupes' => \__( 'Found duplicated words: %s', 'autodescription' ),
358
+ ],
359
+ 'reason' => [
360
+ 'empty' => \__( 'Empty.', 'autodescription' ),
361
+ 'founddupe' => \__( 'Found duplicated words.', 'autodescription' ),
362
+ 'foundmanydupe' => \__( 'Found too many duplicated words.', 'autodescription' ),
363
+ ],
364
+ 'defaults' => [
365
+ 'generated' => [
366
+ 'symbol' => \_x( 'DG', 'Description Generated', 'autodescription' ),
367
+ 'title' => \__( 'Description, generated', 'autodescription' ),
368
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
369
+ 'reason' => \__( 'Automatically generated.', 'autodescription' ),
370
+ 'assess' => [
371
+ 'base' => \__( "It's built using the page content.", 'autodescription' ),
372
+ ],
373
+ ],
374
+ 'emptynoauto' => [
375
+ 'symbol' => \_x( 'D', 'Description', 'autodescription' ),
376
+ 'title' => \__( 'Description', 'autodescription' ),
377
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
378
+ 'reason' => \__( 'Empty.', 'autodescription' ),
379
+ 'assess' => [
380
+ 'noauto' => \__( 'No page description is set.', 'autodescription' ),
381
+ ],
382
+ ],
383
+ 'custom' => [
384
+ 'symbol' => \_x( 'D', 'Description', 'autodescription' ),
385
+ 'title' => \__( 'Description', 'autodescription' ),
386
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
387
+ 'reason' => \__( 'Obtained from page SEO meta input.', 'autodescription' ),
388
+ 'assess' => [
389
+ 'base' => \__( "It's built from page SEO meta input.", 'autodescription' ),
390
+ ],
391
+ ],
392
+ ],
393
+ ]
394
+ );
395
+
396
+ $desc_args = [
397
+ 'id' => static::$query['id'],
398
+ ];
399
+
400
+ // TODO instead of getting values from the options API, why don't we store the parameters and allow them to be modified?
401
+ // This way, we can implement AJAX SEO bar items...
402
+ $desc = static::$tsf->get_description_from_custom_field( $desc_args, false );
403
+
404
+ if ( strlen( $desc ) ) {
405
+ $item = $cache['defaults']['custom'];
406
+
407
+ if ( $this->query_cache['states']['ishome'] ) {
408
+ // Don't use cache here, only one page can have this state.
409
+ if ( static::$tsf->get_option( 'homepage_description' ) ) {
410
+ $item['assess']['homepage'] = \__( 'The description inputted at the SEO Settings screen is used.', 'autodescription' );
411
+ } else {
412
+ $item['assess']['homepage'] = \__( 'The description inputted at the Edit Page screen is used.', 'autodescription' );
413
+ }
414
+ }
415
+ } elseif ( ! static::$tsf->is_auto_description_enabled( $desc_args ) ) {
416
+ $item = $cache['defaults']['emptynoauto'];
417
+
418
+ // No description is found. There's no need to continue parsing.
419
+ return $item;
420
+ } else {
421
+ $item = $cache['defaults']['generated'];
422
+
423
+ $desc = static::$tsf->get_generated_description( $desc_args, false );
424
+
425
+ if ( ! strlen( $desc ) ) {
426
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
427
+ $item['reason'] = $cache['reason']['empty'];
428
+
429
+ // This is now inaccurate, purge it.
430
+ // TODO consider alternative? "It TRIED to build it from...."?
431
+ unset( $item['assess']['base'] );
432
+
433
+ if ( static::$tsf->uses_page_builder( static::$query['id'] ) ) {
434
+ $item['assess']['empty'] = $cache['assess']['builder'];
435
+ } elseif ( static::$tsf->is_protected( static::$query['id'] ) ) {
436
+ $item['assess']['empty'] = $cache['assess']['protected'];
437
+ } else {
438
+ $item['assess']['empty'] = $cache['assess']['empty'];
439
+ }
440
+
441
+ // No description is found. There's no need to continue parsing.
442
+ return $item;
443
+ } else {
444
+ if ( ! empty( $this->query_cache['post']->post_excerpt ) ) {
445
+ $item['assess']['base'] = $cache['assess']['excerpt'];
446
+ }
447
+ }
448
+ }
449
+
450
+ // Fetch words that are outputted more than 3 times.
451
+ $duplicated_words = static::$tsf->get_word_count( $desc, 3, 5, $cache['params']['dupe_short'] );
452
+
453
+ if ( $duplicated_words ) {
454
+ $dupes = [];
455
+ foreach ( $duplicated_words as $_dw ) :
456
+ // Keep abbreviations... WordPress, make multibyte support mandatory already.
457
+ // $_word = ctype_upper( reset( $_dw ) ) ? reset( $_dw ) : mb_strtolower( reset( $_dw ) );
458
+
459
+ $dupes[] = sprintf(
460
+ /* translators: 1: Word found, 2: Occurrences */
461
+ \esc_attr__( '&#8220;%1$s&#8221; is used %2$d times.', 'autodescription' ),
462
+ \esc_attr( key( $_dw ) ),
463
+ reset( $_dw )
464
+ );
465
+ endforeach;
466
+
467
+ $item['assess']['dupe'] = implode( ' ', $dupes );
468
+
469
+ $max = max( $duplicated_words );
470
+ $max = reset( $max );
471
+
472
+ if ( $max > 3 || count( $duplicated_words ) > 1 ) {
473
+ // This must be resolved.
474
+ $item['reason'] = $cache['reason']['foundmanydupe'];
475
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
476
+ return $item;
477
+ } else {
478
+ $item['reason'] = $cache['reason']['founddupe'];
479
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
480
+ }
481
+ }
482
+
483
+ $guidelines = static::$tsf->get_input_guidelines( $this->query_cache['states']['locale'] )['description']['search']['chars'];
484
+ $guidelines_i18n = static::get_cache( 'general/i18n/inputguidelines' );
485
+
486
+ $desc_len = mb_strlen(
487
+ html_entity_decode(
488
+ \wp_specialchars_decode( static::$tsf->s_description_raw( $desc ), ENT_QUOTES ),
489
+ ENT_NOQUOTES
490
+ )
491
+ );
492
+
493
+ if ( $desc_len < $guidelines['lower'] ) {
494
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
495
+ $item['reason'] = $guidelines_i18n['shortdot']['farTooShort'];
496
+ $length_i18n = $guidelines_i18n['long']['farTooShort'];
497
+ } elseif ( $desc_len < $guidelines['goodLower'] ) {
498
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
499
+ $item['reason'] = $guidelines_i18n['shortdot']['tooShort'];
500
+ $length_i18n = $guidelines_i18n['long']['tooShort'];
501
+ } elseif ( $desc_len > $guidelines['upper'] ) {
502
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
503
+ $item['reason'] = $guidelines_i18n['shortdot']['farTooLong'];
504
+ $length_i18n = $guidelines_i18n['long']['farTooLong'];
505
+ } elseif ( $desc_len > $guidelines['goodUpper'] ) {
506
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
507
+ $item['reason'] = $guidelines_i18n['shortdot']['tooLong'];
508
+ $length_i18n = $guidelines_i18n['long']['tooLong'];
509
+ } else {
510
+ // Use unaltered reason and status.
511
+ $length_i18n = $guidelines_i18n['long']['good'];
512
+ }
513
+
514
+ $item['assess']['length'] = sprintf(
515
+ $cache['params']['disclaim'],
516
+ $length_i18n,
517
+ $cache['params']['estimated']
518
+ );
519
+
520
+ return $item;
521
+ }
522
+
523
+ /**
524
+ * Runs description tests.
525
+ *
526
+ * @since 4.0.0
527
+ * @see test_title() for return value.
528
+ *
529
+ * @return array $item
530
+ */
531
+ protected function test_indexing() {
532
+
533
+ $cache = static::get_cache( 'page/indexing/defaults' ) ?: static::set_cache(
534
+ 'page/indexing/defaults',
535
+ [
536
+ 'params' => [],
537
+ 'assess' => [
538
+ 'robotstxt' => \__( 'The robots.txt file is nonstandard, and may still direct search engines differently.', 'autodescription' ),
539
+ 'notpublic' => \__( 'WordPress discourages crawling via the Reading Settings.', 'autodescription' ),
540
+ 'site' => \__( 'Indexing is discouraged for the whole site at the SEO Settings screen.', 'autodescription' ),
541
+ 'posttype' => \__( 'Indexing is discouraged for this post type at the SEO Settings screen.', 'autodescription' ),
542
+ 'protected' => \__( 'The page is protected, so indexing is discouraged.', 'autodescription' ),
543
+ 'override' => \__( 'The page SEO meta input overrides the indexing state.', 'autodescription' ),
544
+ 'canonicalurl' => \__( 'A custom canonical URL is set that points to another page.', 'autodescription' ),
545
+ ],
546
+ 'reason' => [
547
+ 'notpublic' => \__( 'WordPress overrides the robots directive.', 'autodescription' ),
548
+ 'protected' => \__( 'The page is protected.', 'autodescription' ),
549
+ 'notpublished' => \__( 'The page is not published.', 'autodescription' ),
550
+ 'canonicalurl' => \__( 'The canonical URL points to another page.', 'autodescription' ),
551
+ ],
552
+ 'defaults' => [
553
+ 'index' => [
554
+ 'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
555
+ 'title' => \__( 'Indexing', 'autodescription' ),
556
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
557
+ 'reason' => \__( 'Page may be indexed.', 'autodescription' ),
558
+ 'assess' => [
559
+ 'base' => \__( 'The robots meta tag allows indexing.', 'autodescription' ),
560
+ ],
561
+ ],
562
+ 'noindex' => [
563
+ 'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
564
+ 'title' => \__( 'Indexing', 'autodescription' ),
565
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
566
+ 'reason' => \__( 'Page may not be indexed.', 'autodescription' ),
567
+ 'assess' => [
568
+ 'base' => \__( 'The robots meta tag does not allow indexing.', 'autodescription' ),
569
+ ],
570
+ ],
571
+ 'draft' => [
572
+ 'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
573
+ 'title' => \__( 'Indexing', 'autodescription' ),
574
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
575
+ 'reason' => \__( 'Page is invisible.', 'autodescription' ),
576
+ 'assess' => [
577
+ 'base' => \__( "This page isn't published and can't be found publicly.", 'autodescription' ),
578
+ ],
579
+ ],
580
+ ],
581
+ ]
582
+ );
583
+
584
+ $robots_global = static::get_cache( 'general/detect/robotsglobal' );
585
+
586
+ if ( $this->query_cache['states']['isdraft'] ) {
587
+ $item = $cache['defaults']['draft'];
588
+ // TODO Really stop asserting from here?
589
+ return $item;
590
+ } elseif ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
591
+ $item = $cache['defaults']['noindex'];
592
+ } else {
593
+ $item = $cache['defaults']['index'];
594
+ }
595
+
596
+ if ( ! $robots_global['blogpublic'] ) {
597
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
598
+ $item['reason'] = $cache['reason']['notpublic'];
599
+
600
+ unset( $item['assess']['base'] );
601
+
602
+ $item['assess']['notpublic'] = $cache['assess']['notpublic'];
603
+
604
+ // Change symbol to grab attention
605
+ $item['symbol'] = '!!!';
606
+
607
+ // Let the user resolve this first, everything's moot hereafter.
608
+ return $item;
609
+ }
610
+
611
+ if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
612
+ // Don't trickle when noindex is not set, as this may be filtered.
613
+ if ( $this->query_cache['states']['isprotected'] ) {
614
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
615
+ $item['reason'] = $cache['reason']['protected'];
616
+
617
+ $item['assess']['protected'] = $cache['assess']['protected'];
618
+
619
+ return $item;
620
+ }
621
+ }
622
+
623
+ if ( $robots_global['site']['noindex'] ) {
624
+ // Status is already set.
625
+ $item['assess']['site'] = $cache['assess']['site'];
626
+ }
627
+
628
+ if ( $this->query_cache['states']['ishome'] ) {
629
+ // Status is already set.
630
+ if ( static::$tsf->get_option( 'homepage_noindex' ) ) {
631
+ // Don't use cache as this only runs once.
632
+ $item['assess']['homepage'] = \__( 'Indexing is discouraged for the homepage at the SEO Settings screen.', 'autodescription' );
633
+ }
634
+ }
635
+
636
+ if ( isset( $robots_global['posttype']['noindex'][ static::$query['post_type'] ] ) ) {
637
+ // Status is already set.
638
+ $item['assess']['posttype'] = $cache['assess']['posttype'];
639
+ }
640
+
641
+ if ( $this->query_cache['meta']['_genesis_canonical_uri'] ) {
642
+ $permalink = static::$tsf->create_canonical_url( [
643
+ 'id' => static::$query['id'],
644
+ 'get_custom_field' => false,
645
+ ] );
646
+ // We create it because filters may apply.
647
+ $canonical = static::$tsf->create_canonical_url( [
648
+ 'id' => static::$query['id'],
649
+ 'get_custom_field' => true,
650
+ ] );
651
+ if ( $permalink !== $canonical ) {
652
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
653
+ $item['reason'] = $cache['reason']['canonicalurl'];
654
+
655
+ $item['assess']['protected'] = $cache['assess']['canonicalurl'];
656
+ }
657
+ }
658
+
659
+ if ( 0 !== static::$tsf->s_qubit( $this->query_cache['meta']['_genesis_noindex'] ) ) {
660
+ // Status is already set.
661
+
662
+ // Don't assert posttype, homepage, nor site as "blocking" if there's an overide.
663
+ unset(
664
+ $item['assess']['posttype'],
665
+ $item['assess']['homepage'],
666
+ $item['assess']['site']
667
+ );
668
+
669
+ $item['assess']['override'] = $cache['assess']['override'];
670
+ }
671
+
672
+ if ( ! $this->query_cache['states']['robotsmeta']['noindex'] && $robots_global['hasrobotstxt'] ) {
673
+ // Don't change status, we do not parse the robots.txt file. Merely disclaim.
674
+ $item['assess']['robotstxt'] = $cache['assess']['robotstxt'];
675
+ }
676
+
677
+ return $item;
678
+ }
679
+
680
+ /**
681
+ * Runs following tests.
682
+ *
683
+ * @since 4.0.0
684
+ * @see test_title() for return value.
685
+ *
686
+ * @return array $item
687
+ */
688
+ protected function test_following() {
689
+
690
+ $cache = static::get_cache( 'page/following/defaults' ) ?: static::set_cache(
691
+ 'page/following/defaults',
692
+ [
693
+ 'params' => [],
694
+ 'assess' => [
695
+ 'robotstxt' => \__( 'The robots.txt file is nonstandard, and may still direct search engines differently.', 'autodescription' ),
696
+ 'notpublic' => \__( 'WordPress discourages crawling via the Reading Settings.', 'autodescription' ),
697
+ 'site' => \__( 'Link following is discouraged for the whole site at the SEO Settings screen.', 'autodescription' ),
698
+ 'posttype' => \__( 'Link following is discouraged for this post type at the SEO Settings screen.', 'autodescription' ),
699
+ 'override' => \__( 'The page SEO meta input overrides the link following state.', 'autodescription' ),
700
+ 'noindex' => \__( 'The page may not be indexed, this may also discourage link following.', 'autodescription' ),
701
+ ],
702
+ 'reason' => [
703
+ 'notpublic' => \__( 'WordPress overrides the robots directive.', 'autodescription' ),
704
+ 'notpublished' => \__( 'The page is not published.', 'autodescription' ),
705
+ ],
706
+ 'defaults' => [
707
+ 'follow' => [
708
+ 'symbol' => \_x( 'F', 'Following', 'autodescription' ),
709
+ 'title' => \__( 'Following', 'autodescription' ),
710
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
711
+ 'reason' => \__( 'Page links may be followed.', 'autodescription' ),
712
+ 'assess' => [
713
+ 'base' => \__( 'The robots meta tag allows link following.', 'autodescription' ),
714
+ ],
715
+ ],
716
+ 'nofollow' => [
717
+ 'symbol' => \_x( 'F', 'Following', 'autodescription' ),
718
+ 'title' => \__( 'Following', 'autodescription' ),
719
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
720
+ 'reason' => \__( 'Page links may not be followed.', 'autodescription' ),
721
+ 'assess' => [
722
+ 'base' => \__( 'The robots meta tag does not allow link following.', 'autodescription' ),
723
+ ],
724
+ ],
725
+ 'draft' => [
726
+ 'symbol' => \_x( 'F', 'Following', 'autodescription' ),
727
+ 'title' => \__( 'Following', 'autodescription' ),
728
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
729
+ 'reason' => \__( 'Page is invisible.', 'autodescription' ),
730
+ 'assess' => [
731
+ 'base' => \__( "This page isn't published and can't be found publicly.", 'autodescription' ),
732
+ ],
733
+ ],
734
+ ],
735
+ ]
736
+ );
737
+
738
+ $robots_global = static::get_cache( 'general/detect/robotsglobal' );
739
+
740
+ if ( $this->query_cache['states']['isdraft'] ) {
741
+ $item = $cache['defaults']['draft'];
742
+ // TODO Really stop asserting from here?
743
+ return $item;
744
+ } elseif ( $this->query_cache['states']['robotsmeta']['nofollow'] ) {
745
+ $item = $cache['defaults']['nofollow'];
746
+ } else {
747
+ $item = $cache['defaults']['follow'];
748
+ }
749
+
750
+ if ( ! $robots_global['blogpublic'] ) {
751
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
752
+ $item['reason'] = $cache['reason']['notpublic'];
753
+
754
+ unset( $item['assess']['base'] );
755
+
756
+ $item['assess']['notpublic'] = $cache['assess']['notpublic'];
757
+
758
+ // Change symbol to grab attention
759
+ $item['symbol'] = '!!!';
760
+
761
+ // Let the user resolve this first, everything's moot hereafter.
762
+ return $item;
763
+ }
764
+
765
+ if ( $robots_global['site']['nofollow'] ) {
766
+ // Status is already set.
767
+ $item['assess']['site'] = $cache['assess']['site'];
768
+ }
769
+
770
+ if ( $this->query_cache['states']['ishome'] ) {
771
+ // Status is already set.
772
+ if ( static::$tsf->get_option( 'homepage_nofollow' ) ) {
773
+ // Don't use cache as this only runs once.
774
+ $item['assess']['homepage'] = \__( 'Link following is discouraged for the homepage at the SEO Settings screen.', 'autodescription' );
775
+ }
776
+ }
777
+
778
+ if ( isset( $robots_global['posttype']['nofollow'][ static::$query['post_type'] ] ) ) {
779
+ // Status is already set.
780
+ $item['assess']['posttype'] = $cache['assess']['posttype'];
781
+ }
782
+
783
+ if ( 0 !== static::$tsf->s_qubit( $this->query_cache['meta']['_genesis_nofollow'] ) ) {
784
+ // Status is already set.
785
+
786
+ // Don't assert posttype, homepage, nor site as "blocking" if there's an overide.
787
+ unset(
788
+ $item['assess']['posttype'],
789
+ $item['assess']['homepage'],
790
+ $item['assess']['site']
791
+ );
792
+
793
+ $item['assess']['override'] = $cache['assess']['override'];
794
+ }
795
+
796
+ if ( ! $this->query_cache['states']['robotsmeta']['nofollow'] ) {
797
+ if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
798
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
799
+ $item['assess']['noindex'] = $cache['assess']['noindex'];
800
+ }
801
+
802
+ if ( $robots_global['hasrobotstxt'] ) {
803
+ // Don't change status, we do not parse the robots.txt file. Merely disclaim.
804
+ $item['assess']['robotstxt'] = $cache['assess']['robotstxt'];
805
+ }
806
+ }
807
+
808
+ return $item;
809
+ }
810
+
811
+ /**
812
+ * Runs archiving tests.
813
+ *
814
+ * @since 4.0.0
815
+ * @see test_title() for return value.
816
+ *
817
+ * @return array $item
818
+ */
819
+ protected function test_archiving() {
820
+
821
+ $cache = static::get_cache( 'page/archiving/defaults' ) ?: static::set_cache(
822
+ 'page/archiving/defaults',
823
+ [
824
+ 'params' => [],
825
+ 'assess' => [
826
+ 'robotstxt' => \__( 'The robots.txt file is nonstandard, and may still direct search engines differently.', 'autodescription' ),
827
+ 'notpublic' => \__( 'WordPress discourages crawling via the Reading Settings.', 'autodescription' ),
828
+ 'site' => \__( 'Archiving is discouraged for the whole site at the SEO Settings screen.', 'autodescription' ),
829
+ 'posttype' => \__( 'Archiving is discouraged for this post type at the SEO Settings screen.', 'autodescription' ),
830
+ 'override' => \__( 'The page SEO meta input overrides the archiving state.', 'autodescription' ),
831
+ 'noindex' => \__( 'The page may not be indexed, this may also discourage archiving.', 'autodescription' ),
832
+ ],
833
+ 'reason' => [
834
+ 'notpublic' => \__( 'WordPress overrides the robots directive.', 'autodescription' ),
835
+ 'notpublished' => \__( 'The page is not published.', 'autodescription' ),
836
+ ],
837
+ 'defaults' => [
838
+ 'archive' => [
839
+ 'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
840
+ 'title' => \__( 'Archiving', 'autodescription' ),
841
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
842
+ 'reason' => \__( 'Page may be archived.', 'autodescription' ),
843
+ 'assess' => [
844
+ 'base' => \__( 'The robots meta tag allows archiving.', 'autodescription' ),
845
+ ],
846
+ ],
847
+ 'noarchive' => [
848
+ 'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
849
+ 'title' => \__( 'Archiving', 'autodescription' ),
850
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
851
+ 'reason' => \__( 'Page may not be archived.', 'autodescription' ),
852
+ 'assess' => [
853
+ 'base' => \__( 'The robots meta tag does not allow archiving.', 'autodescription' ),
854
+ ],
855
+ ],
856
+ 'draft' => [
857
+ 'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
858
+ 'title' => \__( 'Archiving', 'autodescription' ),
859
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
860
+ 'reason' => \__( 'Page is invisible.', 'autodescription' ),
861
+ 'assess' => [
862
+ 'base' => \__( "This page isn't published and can't be found publicly.", 'autodescription' ),
863
+ ],
864
+ ],
865
+ ],
866
+ ]
867
+ );
868
+
869
+ $robots_global = static::get_cache( 'general/detect/robotsglobal' );
870
+
871
+ if ( $this->query_cache['states']['isdraft'] ) {
872
+ $item = $cache['defaults']['draft'];
873
+ // TODO Really stop asserting from here?
874
+ return $item;
875
+ } elseif ( $this->query_cache['states']['robotsmeta']['noarchive'] ) {
876
+ $item = $cache['defaults']['noarchive'];
877
+ } else {
878
+ $item = $cache['defaults']['archive'];
879
+ }
880
+
881
+ if ( ! $robots_global['blogpublic'] ) {
882
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
883
+ $item['reason'] = $cache['reason']['notpublic'];
884
+
885
+ unset( $item['assess']['base'] );
886
+
887
+ $item['assess']['notpublic'] = $cache['assess']['notpublic'];
888
+
889
+ // Change symbol to grab attention
890
+ $item['symbol'] = '!!!';
891
+
892
+ // Let the user resolve this first, everything's moot hereafter.
893
+ return $item;
894
+ }
895
+
896
+ if ( $robots_global['site']['noarchive'] ) {
897
+ // Status is already set.
898
+ $item['assess']['site'] = $cache['assess']['site'];
899
+ }
900
+
901
+ if ( $this->query_cache['states']['ishome'] ) {
902
+ // Status is already set.
903
+ if ( static::$tsf->get_option( 'homepage_noarchive' ) ) {
904
+ // Don't use cache as this only runs once.
905
+ $item['assess']['homepage'] = \__( 'Archiving is discouraged for the homepage at the SEO Settings screen.', 'autodescription' );
906
+ }
907
+ }
908
+
909
+ if ( isset( $robots_global['posttype']['noarchive'][ static::$query['post_type'] ] ) ) {
910
+ // Status is already set.
911
+ $item['assess']['posttype'] = $cache['assess']['posttype'];
912
+ }
913
+
914
+ if ( 0 !== static::$tsf->s_qubit( $this->query_cache['meta']['_genesis_noarchive'] ) ) {
915
+ // Status is already set.
916
+
917
+ // Don't assert posttype, homepage, nor site as "blocking" if there's an overide.
918
+ unset(
919
+ $item['assess']['posttype'],
920
+ $item['assess']['homepage'],
921
+ $item['assess']['site']
922
+ );
923
+
924
+ $item['assess']['override'] = $cache['assess']['override'];
925
+ }
926
+
927
+ if ( ! $this->query_cache['states']['robotsmeta']['noarchive'] ) {
928
+ if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
929
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
930
+ $item['assess']['noindex'] = $cache['assess']['noindex'];
931
+ }
932
+
933
+ if ( $robots_global['hasrobotstxt'] ) {
934
+ // Don't change status, we do not parse the robots.txt file. Merely disclaim.
935
+ $item['assess']['robotstxt'] = $cache['assess']['robotstxt'];
936
+ }
937
+ }
938
+
939
+ return $item;
940
+ }
941
+
942
+ /**
943
+ * Runs redirect tests.
944
+ *
945
+ * @since 4.0.0
946
+ * @see test_title() for return value.
947
+ *
948
+ * @return array $item
949
+ */
950
+ protected function test_redirect() {
951
+
952
+ if ( empty( $this->query_cache['meta']['redirect'] ) ) {
953
+ return static::get_cache( 'page/redirect/default/0' ) ?: static::set_cache(
954
+ 'page/redirect/default/0',
955
+ [
956
+ 'symbol' => \_x( 'R', 'Redirect', 'autodescription' ),
957
+ 'title' => \__( 'Redirection', 'autodescription' ),
958
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
959
+ 'reason' => \__( 'Page does not redirect visitors.', 'autodescription' ),
960
+ 'assess' => [
961
+ 'redirect' => \__( 'All visitors and crawlers may access this page.', 'autodescription' ),
962
+ ],
963
+ 'meta' => [
964
+ 'blocking' => false,
965
+ ],
966
+ ]
967
+ );
968
+ } else {
969
+ return static::get_cache( 'post/redirect/default/1' ) ?: static::set_cache(
970
+ 'post/redirect/default/1',
971
+ [
972
+ 'symbol' => \_x( 'R', 'Redirect', 'autodescription' ),
973
+ 'title' => \__( 'Redirection', 'autodescription' ),
974
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
975
+ 'reason' => \__( 'Page redirects visitors.', 'autodescription' ),
976
+ 'assess' => [
977
+ 'redirect' => \__( 'All visitors and crawlers are being redirected. So, no other SEO enhancements are effective.', 'autodescription' ),
978
+ ],
979
+ 'meta' => [
980
+ 'blocking' => true,
981
+ ],
982
+ ]
983
+ );
984
+ }
985
+ }
986
+ }
inc/classes/builders/seobar-term.class.php ADDED
@@ -0,0 +1,930 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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 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
42
+ * @abstract
43
+ * @var array All known tests.
44
+ */
45
+ public static $tests = [ 'title', 'description', 'indexing', 'following', 'archiving', 'redirect' ];
46
+
47
+ /**
48
+ * Primes the cache.
49
+ *
50
+ * @since 4.0.0
51
+ * @abstract
52
+ */
53
+ protected function prime_cache() {
54
+ static::get_cache( 'general/i18n/inputguidelines' )
55
+ or static::set_cache(
56
+ 'general/i18n/inputguidelines',
57
+ static::$tsf->get_input_guidelines_i18n()
58
+ );
59
+
60
+ static::get_cache( 'general/detect/robotsglobal' )
61
+ or static::set_cache(
62
+ 'general/detect/robotsglobal',
63
+ [
64
+ 'hasrobotstxt' => static::$tsf->has_robots_txt(),
65
+ 'blogpublic' => static::$tsf->is_blog_public(),
66
+ 'site' => [
67
+ 'noindex' => static::$tsf->get_option( 'site_noindex' ),
68
+ 'nofollow' => static::$tsf->get_option( 'site_nofollow' ),
69
+ 'noarchive' => static::$tsf->get_option( 'site_noarchive' ),
70
+ ],
71
+ 'posttype' => [
72
+ 'noindex' => static::$tsf->get_option( static::$tsf->get_robots_post_type_option_id( 'noindex' ) ),
73
+ 'nofollow' => static::$tsf->get_option( static::$tsf->get_robots_post_type_option_id( 'nofollow' ) ),
74
+ 'noarchive' => static::$tsf->get_option( static::$tsf->get_robots_post_type_option_id( 'noarchive' ) ),
75
+ ],
76
+ 'postcat' => [
77
+ 'noindex' => static::$tsf->get_option( 'category_noindex' ),
78
+ 'nofollow' => static::$tsf->get_option( 'category_nofollow' ),
79
+ 'noarchive' => static::$tsf->get_option( 'category_noarchive' ),
80
+ ],
81
+ 'posttag' => [
82
+ 'noindex' => static::$tsf->get_option( 'tag_noindex' ),
83
+ 'nofollow' => static::$tsf->get_option( 'tag_nofollow' ),
84
+ 'noarchive' => static::$tsf->get_option( 'tag_noarchive' ),
85
+ ],
86
+ ]
87
+ );
88
+ }
89
+
90
+ /**
91
+ * Primes the current query cache.
92
+ *
93
+ * @since 4.0.0
94
+ * @abstract
95
+ *
96
+ * @param array $query_cache The current query cache. Passed by reference.
97
+ */
98
+ protected function prime_query_cache( array &$query_cache = [] ) {
99
+
100
+ $term = \get_term( static::$query['id'], static::$query['taxonomy'] );
101
+
102
+ $query_cache = [
103
+ 'term' => $term,
104
+ 'meta' => static::$tsf->get_term_meta( static::$query['id'], true ), // Use TSF cache--TSF initializes it anyway.
105
+ 'states' => [
106
+ 'locale' => \get_locale(),
107
+ 'isempty' => empty( $term->count ),
108
+ 'posttypes' => static::$tsf->get_post_types_from_taxonomy( static::$query['taxonomy'] ),
109
+ 'robotsmeta' => array_merge(
110
+ [
111
+ 'noindex' => false,
112
+ 'nofollow' => false,
113
+ 'noarchive' => false,
114
+ ],
115
+ static::$tsf->robots_meta( [
116
+ 'id' => static::$query['id'],
117
+ 'taxonomy' => static::$query['taxonomy'],
118
+ ] )
119
+ ),
120
+ ],
121
+ ];
122
+ }
123
+
124
+ /**
125
+ * Tests for blocking redirection.
126
+ *
127
+ * @since 4.0.0
128
+ * @abstract
129
+ *
130
+ * @return bool True if there's a blocking redirect, false otherwise.
131
+ */
132
+ protected function has_blocking_redirect() {
133
+ return ! empty( $this->query_cache['meta']['redirect'] );
134
+ }
135
+
136
+ /**
137
+ * Runs title tests.
138
+ *
139
+ * @since 4.0.0
140
+ *
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.
148
+ * }
149
+ */
150
+ protected function test_title() {
151
+
152
+ $cache = static::get_cache( 'term/title/defaults' ) ?: static::set_cache(
153
+ 'term/title/defaults',
154
+ [
155
+ 'params' => [
156
+ 'untitled' => static::$tsf->get_static_untitled_title(),
157
+ 'blogname_quoted' => preg_quote( static::$tsf->get_blogname(), '/' ),
158
+ 'prefixed' => static::$tsf->use_generated_archive_prefix(),
159
+ /* translators: 1 = An assessment, 2 = Disclaimer, e.g. "take it with a grain of salt" */
160
+ 'disclaim' => \__( '%1$s (%2$s)', 'autodescription' ),
161
+ 'estimated' => \__( 'Estimated from the number of characters found. The pixel counter asserts the true length.', 'autodescription' ),
162
+ ],
163
+ 'assess' => [
164
+ 'empty' => \__( 'No title could be fetched.', 'autodescription' ),
165
+ 'untitled' => \__( 'No title could be fetched, "Untitled" is used instead.', 'autodescription' ), // TODO use [params][untitled]?
166
+ 'prefixed' => \__( 'A term label prefix is automatically added which increases the length.', 'autodescription' ),
167
+ 'branding' => [
168
+ 'not' => \__( "It's not branded. Search engines may ignore your title.", 'autodescription' ),
169
+ 'manual' => \__( "It's manually branded.", 'autodescription' ),
170
+ 'automatic' => \__( "It's automatically branded.", 'autodescription' ),
171
+ ],
172
+ 'duplicated' => \__( 'The blog name is found multiple times.', 'autodescription' ),
173
+ ],
174
+ 'reason' => [
175
+ 'incomplete' => \__( 'Incomplete.', 'autodescription' ),
176
+ 'duplicated' => \__( 'The branding is duplicated.', 'autodescription' ),
177
+ 'notbranded' => \__( 'Not branded.', 'autodescription' ),
178
+ ],
179
+ 'defaults' => [
180
+ 'generated' => [
181
+ 'symbol' => \_x( 'TG', 'Title Generated', 'autodescription' ),
182
+ 'title' => \__( 'Title, generated', 'autodescription' ),
183
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
184
+ 'reason' => \__( 'Automatically generated.', 'autodescription' ),
185
+ 'assess' => [
186
+ 'base' => \__( "It's built using the page title.", 'autodescription' ),
187
+ ],
188
+ ],
189
+ 'custom' => [
190
+ 'symbol' => \_x( 'T', 'Title', 'autodescription' ),
191
+ 'title' => \__( 'Title', 'autodescription' ),
192
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
193
+ 'reason' => \__( 'Obtained from term SEO meta input.', 'autodescription' ),
194
+ 'assess' => [
195
+ 'base' => \__( "It's built from term SEO meta input.", 'autodescription' ),
196
+ ],
197
+ ],
198
+ ],
199
+ ]
200
+ );
201
+
202
+ $title_args = [
203
+ 'id' => static::$query['id'],
204
+ 'taxonomy' => static::$query['taxonomy'],
205
+ ];
206
+
207
+ // TODO instead of getting values from the options API, why don't we store the parameters and allow them to be modified?
208
+ // This way, we can implement AJAX SEO bar items...
209
+ $title_part = static::$tsf->get_filtered_raw_custom_field_title( $title_args, false );
210
+
211
+ if ( strlen( $title_part ) ) {
212
+ $item = $cache['defaults']['custom'];
213
+ } else {
214
+ $item = $cache['defaults']['generated'];
215
+
216
+ // Move this to defaults cache? It'll make the code unreadable, though...
217
+ if ( $cache['params']['prefixed'] ) {
218
+ $item['assess']['prefixed'] = $cache['assess']['prefixed'];
219
+ }
220
+
221
+ $title_part = static::$tsf->get_filtered_raw_generated_title( $title_args, false );
222
+ }
223
+
224
+ if ( ! $title_part ) {
225
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
226
+ $item['reason'] = $cache['reason']['incomplete'];
227
+ $item['assess']['empty'] = $cache['assess']['empty'];
228
+
229
+ // Further assessments must be made later. Halt assertion here to prevent confusion.
230
+ return $item;
231
+ } elseif ( $title_part === $cache['params']['untitled'] ) {
232
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
233
+ $item['reason'] = $cache['reason']['incomplete'];
234
+ $item['assess']['untitled'] = $cache['assess']['untitled'];
235
+
236
+ // Further assessments must be made later. Halt assertion here to prevent confusion.
237
+ return $item;
238
+ }
239
+
240
+ $title = $title_part;
241
+
242
+ if ( static::$tsf->use_title_branding( $title_args ) ) {
243
+ $_title_before = $title;
244
+ static::$tsf->merge_title_branding( $title, $title_args );
245
+
246
+ // Absence assertion is done after this.
247
+ if ( $title === $_title_before ) {
248
+ $item['assess']['branding'] = $cache['assess']['branding']['manual'];
249
+ } else {
250
+ $item['assess']['branding'] = $cache['assess']['branding']['automatic'];
251
+ }
252
+ } else {
253
+ $item['assess']['branding'] = $cache['assess']['branding']['manual'];
254
+ }
255
+
256
+ $brand_count =
257
+ strlen( $cache['params']['blogname_quoted'] )
258
+ ? preg_match_all(
259
+ "/{$cache['params']['blogname_quoted']}/ui",
260
+ $title,
261
+ $matches
262
+ )
263
+ : 0;
264
+
265
+ if ( ! $brand_count ) {
266
+ // Override branding state.
267
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
268
+ $item['reason'] = $cache['reason']['notbranded'];
269
+ $item['assess']['branding'] = $cache['assess']['branding']['not'];
270
+ } elseif ( $brand_count > 1 ) {
271
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
272
+ $item['reason'] = $cache['reason']['duplicated'];
273
+ $item['assess']['duplicated'] = $cache['assess']['duplicated'];
274
+
275
+ // Further assessments must be made later. Halt assertion here to prevent confusion.
276
+ return $item;
277
+ }
278
+
279
+ $title_len = mb_strlen(
280
+ html_entity_decode(
281
+ \wp_specialchars_decode( static::$tsf->s_title_raw( $title ), ENT_QUOTES ),
282
+ ENT_NOQUOTES
283
+ )
284
+ );
285
+
286
+ $guidelines = static::$tsf->get_input_guidelines( $this->query_cache['states']['locale'] )['title']['search']['chars'];
287
+ $guidelines_i18n = static::get_cache( 'general/i18n/inputguidelines' );
288
+
289
+ if ( $title_len < $guidelines['lower'] ) {
290
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
291
+ $item['reason'] = $guidelines_i18n['shortdot']['farTooShort'];
292
+ $length_i18n = $guidelines_i18n['long']['farTooShort'];
293
+ } elseif ( $title_len < $guidelines['goodLower'] ) {
294
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
295
+ $item['reason'] = $guidelines_i18n['shortdot']['tooShort'];
296
+ $length_i18n = $guidelines_i18n['long']['tooShort'];
297
+ } elseif ( $title_len > $guidelines['upper'] ) {
298
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
299
+ $item['reason'] = $guidelines_i18n['shortdot']['farTooLong'];
300
+ $length_i18n = $guidelines_i18n['long']['farTooLong'];
301
+ } elseif ( $title_len > $guidelines['goodUpper'] ) {
302
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
303
+ $item['reason'] = $guidelines_i18n['shortdot']['tooLong'];
304
+ $length_i18n = $guidelines_i18n['long']['tooLong'];
305
+ } else {
306
+ // Use unaltered reason and status.
307
+ $length_i18n = $guidelines_i18n['long']['good'];
308
+ }
309
+
310
+ $item['assess']['length'] = sprintf(
311
+ $cache['params']['disclaim'],
312
+ $length_i18n,
313
+ $cache['params']['estimated']
314
+ );
315
+
316
+ return $item;
317
+ }
318
+
319
+ /**
320
+ * Runs title tests.
321
+ *
322
+ * @since 4.0.0
323
+ * @see test_title() for return value.
324
+ *
325
+ * @return array $item
326
+ */
327
+ protected function test_description() {
328
+
329
+ $cache = static::get_cache( 'term/description/defaults' ) ?: static::set_cache(
330
+ 'term/description/defaults',
331
+ [
332
+ 'params' => [
333
+ /* translators: 1 = An assessment, 2 = Disclaimer, e.g. "take it with a grain of salt" */
334
+ 'disclaim' => \__( '%1$s (%2$s)', 'autodescription' ),
335
+ 'estimated' => \__( 'Estimated from the number of characters found. The pixel counter asserts the true length.', 'autodescription' ),
336
+ /**
337
+ * @since 2.6.0
338
+ * @param int $dupe_short The minimum stringlength of words to find as dupes.
339
+ */
340
+ 'dupe_short' => (int) \apply_filters( 'the_seo_framework_bother_me_desc_length', 3 ),
341
+ ],
342
+ 'assess' => [
343
+ 'empty' => \__( 'No description could be generated.', 'autodescription' ),
344
+ /* translators: %s = list of duplicated words */
345
+ 'dupes' => \__( 'Found duplicated words: %s', 'autodescription' ),
346
+ ],
347
+ 'reason' => [
348
+ 'empty' => \__( 'Empty.', 'autodescription' ),
349
+ 'founddupe' => \__( 'Found duplicated words.', 'autodescription' ),
350
+ 'foundmanydupe' => \__( 'Found too many duplicated words.', 'autodescription' ),
351
+ ],
352
+ 'defaults' => [
353
+ 'generated' => [
354
+ 'symbol' => \_x( 'DG', 'Description Generated', 'autodescription' ),
355
+ 'title' => \__( 'Description, generated', 'autodescription' ),
356
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
357
+ 'reason' => \__( 'Automatically generated.', 'autodescription' ),
358
+ 'assess' => [
359
+ 'base' => \__( "It's built using the term description field.", 'autodescription' ),
360
+ ],
361
+ ],
362
+ 'emptynoauto' => [
363
+ 'symbol' => \_x( 'D', 'Description', 'autodescription' ),
364
+ 'title' => \__( 'Description', 'autodescription' ),
365
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
366
+ 'reason' => \__( 'Empty.', 'autodescription' ),
367
+ 'assess' => [
368
+ 'noauto' => \__( 'No term description is set.', 'autodescription' ),
369
+ ],
370
+ ],
371
+ 'custom' => [
372
+ 'symbol' => \_x( 'D', 'Description', 'autodescription' ),
373
+ 'title' => \__( 'Description', 'autodescription' ),
374
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
375
+ 'reason' => \__( 'Obtained from term SEO meta input.', 'autodescription' ),
376
+ 'assess' => [
377
+ 'base' => \__( "It's built from term SEO meta input.", 'autodescription' ),
378
+ ],
379
+ ],
380
+ ],
381
+ ]
382
+ );
383
+
384
+ $desc_args = [
385
+ 'id' => static::$query['id'],
386
+ 'taxonomy' => static::$query['taxonomy'],
387
+ ];
388
+
389
+ // TODO instead of getting values from the options API, why don't we store the parameters and allow them to be modified?
390
+ // This way, we can implement AJAX SEO bar items...
391
+ $desc = static::$tsf->get_description_from_custom_field( $desc_args, false );
392
+
393
+ if ( strlen( $desc ) ) {
394
+ $item = $cache['defaults']['custom'];
395
+ } elseif ( ! static::$tsf->is_auto_description_enabled( $desc_args ) ) {
396
+ $item = $cache['defaults']['emptynoauto'];
397
+
398
+ // No description is found. There's no need to continue parsing.
399
+ return $item;
400
+ } else {
401
+ $item = $cache['defaults']['generated'];
402
+
403
+ $desc = static::$tsf->get_generated_description( $desc_args, false );
404
+
405
+ if ( ! strlen( $desc ) ) {
406
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
407
+ $item['reason'] = $cache['reason']['empty'];
408
+
409
+ // This is now inaccurate, purge it.
410
+ // TODO consider alternative? "It TRIED to build it from...."?
411
+ unset( $item['assess']['base'] );
412
+
413
+ $item['assess']['empty'] = $cache['assess']['empty'];
414
+
415
+ // No description is found. There's no need to continue parsing.
416
+ return $item;
417
+ }
418
+ }
419
+
420
+ // Fetch words that are outputted more than 3 times.
421
+ $duplicated_words = static::$tsf->get_word_count( $desc, 3, 5, $cache['params']['dupe_short'] );
422
+
423
+ if ( $duplicated_words ) {
424
+ $dupes = [];
425
+ foreach ( $duplicated_words as $_dw ) :
426
+ // Keep abbreviations... WordPress, make multibyte support mandatory already.
427
+ // $_word = ctype_upper( reset( $_dw ) ) ? reset( $_dw ) : mb_strtolower( reset( $_dw ) );
428
+
429
+ $dupes[] = sprintf(
430
+ /* translators: 1: Word found, 2: Occurrences */
431
+ \esc_attr__( '&#8220;%1$s&#8221; is used %2$d times.', 'autodescription' ),
432
+ \esc_attr( key( $_dw ) ),
433
+ reset( $_dw )
434
+ );
435
+ endforeach;
436
+
437
+ $item['assess']['dupe'] = implode( ' ', $dupes );
438
+
439
+ $max = max( $duplicated_words );
440
+ $max = reset( $max );
441
+
442
+ if ( $max > 3 || count( $duplicated_words ) > 1 ) {
443
+ // This must be resolved.
444
+ $item['reason'] = $cache['reason']['foundmanydupe'];
445
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
446
+ return $item;
447
+ } else {
448
+ $item['reason'] = $cache['reason']['founddupe'];
449
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
450
+ }
451
+ }
452
+
453
+ $guidelines = static::$tsf->get_input_guidelines( $this->query_cache['states']['locale'] )['description']['search']['chars'];
454
+ $guidelines_i18n = static::get_cache( 'general/i18n/inputguidelines' );
455
+
456
+ $desc_len = mb_strlen(
457
+ html_entity_decode(
458
+ \wp_specialchars_decode( static::$tsf->s_description_raw( $desc ), ENT_QUOTES ),
459
+ ENT_NOQUOTES
460
+ )
461
+ );
462
+
463
+ if ( $desc_len < $guidelines['lower'] ) {
464
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
465
+ $item['reason'] = $guidelines_i18n['shortdot']['farTooShort'];
466
+ $length_i18n = $guidelines_i18n['long']['farTooShort'];
467
+ } elseif ( $desc_len < $guidelines['goodLower'] ) {
468
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
469
+ $item['reason'] = $guidelines_i18n['shortdot']['tooShort'];
470
+ $length_i18n = $guidelines_i18n['long']['tooShort'];
471
+ } elseif ( $desc_len > $guidelines['upper'] ) {
472
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
473
+ $item['reason'] = $guidelines_i18n['shortdot']['farTooLong'];
474
+ $length_i18n = $guidelines_i18n['long']['farTooLong'];
475
+ } elseif ( $desc_len > $guidelines['goodUpper'] ) {
476
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
477
+ $item['reason'] = $guidelines_i18n['shortdot']['tooLong'];
478
+ $length_i18n = $guidelines_i18n['long']['tooLong'];
479
+ } else {
480
+ // Use unaltered reason and status.
481
+ $length_i18n = $guidelines_i18n['long']['good'];
482
+ }
483
+
484
+ $item['assess']['length'] = sprintf(
485
+ $cache['params']['disclaim'],
486
+ $length_i18n,
487
+ $cache['params']['estimated']
488
+ );
489
+
490
+ return $item;
491
+ }
492
+
493
+ /**
494
+ * Runs description tests.
495
+ *
496
+ * @since 4.0.0
497
+ * @see test_title() for return value.
498
+ *
499
+ * @return array $item
500
+ */
501
+ protected function test_indexing() {
502
+
503
+ $cache = static::get_cache( 'term/indexing/defaults' ) ?: static::set_cache(
504
+ 'term/indexing/defaults',
505
+ [
506
+ 'params' => [],
507
+ 'assess' => [
508
+ 'robotstxt' => \__( 'The robots.txt file is nonstandard, and may still direct search engines differently.', 'autodescription' ),
509
+ 'notpublic' => \__( 'WordPress discourages crawling via the Reading Settings.', 'autodescription' ),
510
+ 'site' => \__( 'Indexing is discouraged for the whole site at the SEO Settings screen.', 'autodescription' ),
511
+ 'posttypes' => \__( 'Indexing is discouraged for all bound post types to this term at the SEO Settings screen.', 'autodescription' ),
512
+ 'postcats' => \__( 'Indexing is discouraged for all post categories at the SEO Settings screen.', 'autodescription' ),
513
+ 'posttags' => \__( 'Indexing is discouraged for all post tags at the SEO Settings screen.', 'autodescription' ),
514
+ 'override' => \__( 'The term SEO meta input overrides the indexing state.', 'autodescription' ),
515
+ 'empty' => \__( 'No posts are attached to this term, so indexing is disabled.', 'autodescription' ),
516
+ 'emptyoverride' => \__( 'No posts are attached to this term, so indexing should be disabled.', 'autodescription' ),
517
+ 'canonicalurl' => \__( 'A custom canonical URL is set that points to another page.', 'autodescription' ),
518
+ ],
519
+ 'reason' => [
520
+ 'notpublic' => \__( 'WordPress overrides the robots directive.', 'autodescription' ),
521
+ 'empty' => \__( 'The term is empty.', 'autodescription' ),
522
+ 'emptyoverride' => \__( 'The term is empty yet still indexed.', 'autodescription' ),
523
+ 'canonicalurl' => \__( 'The canonical URL points to another page.', 'autodescription' ),
524
+ ],
525
+ 'defaults' => [
526
+ 'index' => [
527
+ 'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
528
+ 'title' => \__( 'Indexing', 'autodescription' ),
529
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
530
+ 'reason' => \__( 'Term may be indexed.', 'autodescription' ),
531
+ 'assess' => [
532
+ 'base' => \__( 'The robots meta tag allows indexing.', 'autodescription' ),
533
+ ],
534
+ ],
535
+ 'noindex' => [
536
+ 'symbol' => \_x( 'I', 'Indexing', 'autodescription' ),
537
+ 'title' => \__( 'Indexing', 'autodescription' ),
538
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
539
+ 'reason' => \__( 'Term may not be indexed.', 'autodescription' ),
540
+ 'assess' => [
541
+ 'base' => \__( 'The robots meta tag does not allow indexing.', 'autodescription' ),
542
+ ],
543
+ ],
544
+ ],
545
+ ]
546
+ );
547
+
548
+ $robots_global = static::get_cache( 'general/detect/robotsglobal' );
549
+
550
+ if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
551
+ $item = $cache['defaults']['noindex'];
552
+ } else {
553
+ $item = $cache['defaults']['index'];
554
+ }
555
+
556
+ if ( ! $robots_global['blogpublic'] ) {
557
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
558
+ $item['reason'] = $cache['reason']['notpublic'];
559
+
560
+ unset( $item['assess']['base'] );
561
+
562
+ $item['assess']['notpublic'] = $cache['assess']['notpublic'];
563
+
564
+ // Change symbol to grab attention
565
+ $item['symbol'] = '!!!';
566
+
567
+ // Let the user resolve this first, everything's moot hereafter.
568
+ return $item;
569
+ }
570
+
571
+ if ( $robots_global['site']['noindex'] ) {
572
+ // Status is already set.
573
+ $item['assess']['site'] = $cache['assess']['site'];
574
+ }
575
+
576
+ // Test all post types bound to the term. Only if all post types are excluded, set this option.
577
+ $_post_type_noindex_set = [];
578
+ foreach ( $this->query_cache['states']['posttypes'] as $_post_type ) {
579
+ $_post_type_noindex_set[] = isset( $robots_global['posttype']['noindex'][ $_post_type ] );
580
+ }
581
+ if ( ! in_array( false, $_post_type_noindex_set, true ) ) {
582
+ // Status is already set.
583
+ $item['assess']['posttypes'] = $cache['assess']['posttypes'];
584
+ }
585
+
586
+ if ( $robots_global['postcat']['noindex'] && 'category' === static::$query['taxonomy'] ) {
587
+ // Status is already set.
588
+ $item['assess']['postcats'] = $cache['assess']['postcats'];
589
+ } elseif ( $robots_global['posttag']['noindex'] && 'post_tag' === static::$query['taxonomy'] ) {
590
+ // Status is already set.
591
+ $item['assess']['posttags'] = $cache['assess']['posttags'];
592
+ }
593
+
594
+ if ( 0 !== static::$tsf->s_qubit( $this->query_cache['meta']['noindex'] ) ) {
595
+ // Status is already set.
596
+
597
+ // Don't assert posttype nor site as "blocking" if there's an overide.
598
+ unset( $item['assess']['site'], $item['assess']['posttypes'], $item['assess']['postcats'], $item['assess']['posttags'] );
599
+
600
+ $item['assess']['override'] = $cache['assess']['override'];
601
+ }
602
+
603
+ if ( $this->query_cache['meta']['canonical'] ) {
604
+ $permalink = static::$tsf->create_canonical_url( [
605
+ 'id' => static::$query['id'],
606
+ 'taxonomy' => static::$query['taxonomy'],
607
+ 'get_custom_field' => false,
608
+ ] );
609
+ // We create it because filters may apply.
610
+ $canonical = static::$tsf->create_canonical_url( [
611
+ 'id' => static::$query['id'],
612
+ 'taxonomy' => static::$query['taxonomy'],
613
+ 'get_custom_field' => true,
614
+ ] );
615
+ if ( $permalink !== $canonical ) {
616
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
617
+ $item['reason'] = $cache['reason']['canonicalurl'];
618
+
619
+ $item['assess']['protected'] = $cache['assess']['canonicalurl'];
620
+ }
621
+ }
622
+
623
+ if ( $this->query_cache['states']['isempty'] ) {
624
+ if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
625
+ // Everything's as intended...
626
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN;
627
+ $item['reason'] = $cache['reason']['empty'];
628
+
629
+ $item['assess']['empty'] = $cache['assess']['empty'];
630
+ } else {
631
+ // Something's wrong. Maybe override, maybe filter, maybe me.
632
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
633
+
634
+ $item['reason'] = $cache['reason']['emptyoverride'];
635
+ $item['assess']['empty'] = $cache['assess']['emptyoverride'];
636
+ }
637
+ }
638
+
639
+ if ( ! $this->query_cache['states']['robotsmeta']['noindex'] && $robots_global['hasrobotstxt'] ) {
640
+ // Don't change status, we do not parse the robots.txt file. Merely disclaim.
641
+ $item['assess']['robotstxt'] = $cache['assess']['robotstxt'];
642
+ }
643
+
644
+ return $item;
645
+ }
646
+
647
+ /**
648
+ * Runs following tests.
649
+ *
650
+ * @since 4.0.0
651
+ * @see test_title() for return value.
652
+ *
653
+ * @return array $item
654
+ */
655
+ protected function test_following() {
656
+
657
+ $cache = static::get_cache( 'term/following/defaults' ) ?: static::set_cache(
658
+ 'term/following/defaults',
659
+ [
660
+ 'params' => [],
661
+ 'assess' => [
662
+ 'robotstxt' => \__( 'The robots.txt file is nonstandard, and may still direct search engines differently.', 'autodescription' ),
663
+ 'notpublic' => \__( 'WordPress discourages crawling via the Reading Settings.', 'autodescription' ),
664
+ 'site' => \__( 'Link following is discouraged for the whole site at the SEO Settings screen.', 'autodescription' ),
665
+ 'posttypes' => \__( 'Link following is discouraged for all bound post types to this term at the SEO Settings screen.', 'autodescription' ),
666
+ 'postcats' => \__( 'Link following is discouraged for all post categories at the SEO Settings screen.', 'autodescription' ),
667
+ 'posttags' => \__( 'Link following is discouraged for all post tags at the SEO Settings screen.', 'autodescription' ),
668
+ 'override' => \__( 'The term SEO meta input overrides the link following state.', 'autodescription' ),
669
+ 'noindex' => \__( 'The term may not be indexed, this may also discourage link following.', 'autodescription' ),
670
+ ],
671
+ 'reason' => [
672
+ 'notpublic' => \__( 'WordPress overrides the robots directive.', 'autodescription' ),
673
+ ],
674
+ 'defaults' => [
675
+ 'follow' => [
676
+ 'symbol' => \_x( 'F', 'Following', 'autodescription' ),
677
+ 'title' => \__( 'Following', 'autodescription' ),
678
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
679
+ 'reason' => \__( 'Term links may be followed.', 'autodescription' ),
680
+ 'assess' => [
681
+ 'base' => \__( 'The robots meta tag allows link following.', 'autodescription' ),
682
+ ],
683
+ ],
684
+ 'nofollow' => [
685
+ 'symbol' => \_x( 'F', 'Following', 'autodescription' ),
686
+ 'title' => \__( 'Following', 'autodescription' ),
687
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
688
+ 'reason' => \__( 'Term links may not be followed.', 'autodescription' ),
689
+ 'assess' => [
690
+ 'base' => \__( 'The robots meta tag does not allow link following.', 'autodescription' ),
691
+ ],
692
+ ],
693
+ ],
694
+ ]
695
+ );
696
+
697
+ $robots_global = static::get_cache( 'general/detect/robotsglobal' );
698
+
699
+ if ( $this->query_cache['states']['robotsmeta']['nofollow'] ) {
700
+ $item = $cache['defaults']['nofollow'];
701
+ } else {
702
+ $item = $cache['defaults']['follow'];
703
+ }
704
+
705
+ if ( ! $robots_global['blogpublic'] ) {
706
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
707
+ $item['reason'] = $cache['reason']['notpublic'];
708
+
709
+ unset( $item['assess']['base'] );
710
+
711
+ $item['assess']['notpublic'] = $cache['assess']['notpublic'];
712
+
713
+ // Change symbol to grab attention
714
+ $item['symbol'] = '!!!';
715
+
716
+ // Let the user resolve this first, everything's moot hereafter.
717
+ return $item;
718
+ }
719
+
720
+ if ( $robots_global['site']['nofollow'] ) {
721
+ // Status is already set.
722
+ $item['assess']['site'] = $cache['assess']['site'];
723
+ }
724
+
725
+ // Test all post types bound to the term. Only if all post types are excluded, set this option.
726
+ $_post_type_nofollow_set = [];
727
+ foreach ( $this->query_cache['states']['posttypes'] as $_post_type ) {
728
+ $_post_type_nofollow_set[] = isset( $robots_global['posttype']['nofollow'][ $_post_type ] );
729
+ }
730
+ if ( ! in_array( false, $_post_type_nofollow_set, true ) ) {
731
+ // Status is already set.
732
+ $item['assess']['posttypes'] = $cache['assess']['posttypes'];
733
+ }
734
+
735
+ if ( $robots_global['postcat']['nofollow'] && 'category' === static::$query['taxonomy'] ) {
736
+ // Status is already set.
737
+ $item['assess']['postcats'] = $cache['assess']['postcats'];
738
+ } elseif ( $robots_global['posttag']['nofollow'] && 'post_tag' === static::$query['taxonomy'] ) {
739
+ // Status is already set.
740
+ $item['assess']['posttags'] = $cache['assess']['posttags'];
741
+ }
742
+
743
+ if ( 0 !== static::$tsf->s_qubit( $this->query_cache['meta']['nofollow'] ) ) {
744
+ // Status is already set.
745
+
746
+ // Don't assert posttype nor site as "blocking" if there's an overide.
747
+ unset( $item['assess']['site'], $item['assess']['posttypes'], $item['assess']['postcats'], $item['assess']['posttags'] );
748
+
749
+ $item['assess']['override'] = $cache['assess']['override'];
750
+ }
751
+
752
+ if ( ! $this->query_cache['states']['robotsmeta']['nofollow'] ) {
753
+ if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
754
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
755
+ $item['assess']['noindex'] = $cache['assess']['noindex'];
756
+ }
757
+
758
+ if ( $robots_global['hasrobotstxt'] ) {
759
+ // Don't change status, we do not parse the robots.txt file. Merely disclaim.
760
+ $item['assess']['robotstxt'] = $cache['assess']['robotstxt'];
761
+ }
762
+ }
763
+
764
+ return $item;
765
+ }
766
+
767
+ /**
768
+ * Runs archiving tests.
769
+ *
770
+ * @since 4.0.0
771
+ * @see test_title() for return value.
772
+ *
773
+ * @return array $item
774
+ */
775
+ protected function test_archiving() {
776
+
777
+ $cache = static::get_cache( 'term/archiving/defaults' ) ?: static::set_cache(
778
+ 'term/archiving/defaults',
779
+ [
780
+ 'params' => [],
781
+ 'assess' => [
782
+ 'robotstxt' => \__( 'The robots.txt file is nonstandard, and may still direct search engines differently.', 'autodescription' ),
783
+ 'notpublic' => \__( 'WordPress discourages crawling via the Reading Settings.', 'autodescription' ),
784
+ 'site' => \__( 'Archiving is discouraged for the whole site at the SEO Settings screen.', 'autodescription' ),
785
+ 'posttypes' => \__( 'Archiving is discouraged for all bound post types to this term at the SEO Settings screen.', 'autodescription' ),
786
+ 'postcats' => \__( 'Archiving is discouraged for all post categories at the SEO Settings screen.', 'autodescription' ),
787
+ 'posttags' => \__( 'Archiving is discouraged for all post tags at the SEO Settings screen.', 'autodescription' ),
788
+ 'override' => \__( 'The term SEO meta input overrides the archiving state.', 'autodescription' ),
789
+ 'noindex' => \__( 'The term may not be indexed, this may also discourage archiving.', 'autodescription' ),
790
+ ],
791
+ 'reason' => [
792
+ 'notpublic' => \__( 'WordPress overrides the robots directive.', 'autodescription' ),
793
+ ],
794
+ 'defaults' => [
795
+ 'archive' => [
796
+ 'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
797
+ 'title' => \__( 'Archiving', 'autodescription' ),
798
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
799
+ 'reason' => \__( 'Term may be archived.', 'autodescription' ),
800
+ 'assess' => [
801
+ 'base' => \__( 'The robots meta tag allows archiving.', 'autodescription' ),
802
+ ],
803
+ ],
804
+ 'noarchive' => [
805
+ 'symbol' => \_x( 'A', 'Archiving', 'autodescription' ),
806
+ 'title' => \__( 'Archiving', 'autodescription' ),
807
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
808
+ 'reason' => \__( 'Term may not be archived.', 'autodescription' ),
809
+ 'assess' => [
810
+ 'base' => \__( 'The robots meta tag does not allow archiving.', 'autodescription' ),
811
+ ],
812
+ ],
813
+ ],
814
+ ]
815
+ );
816
+
817
+ $robots_global = static::get_cache( 'general/detect/robotsglobal' );
818
+
819
+ if ( $this->query_cache['states']['robotsmeta']['noarchive'] ) {
820
+ $item = $cache['defaults']['noarchive'];
821
+ } else {
822
+ $item = $cache['defaults']['archive'];
823
+ }
824
+
825
+ if ( ! $robots_global['blogpublic'] ) {
826
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_BAD;
827
+ $item['reason'] = $cache['reason']['notpublic'];
828
+
829
+ unset( $item['assess']['base'] );
830
+
831
+ $item['assess']['notpublic'] = $cache['assess']['notpublic'];
832
+
833
+ // Change symbol to grab attention
834
+ $item['symbol'] = '!!!';
835
+
836
+ // Let the user resolve this first, everything's moot hereafter.
837
+ return $item;
838
+ }
839
+
840
+ if ( $robots_global['site']['noarchive'] ) {
841
+ // Status is already set.
842
+ $item['assess']['site'] = $cache['assess']['site'];
843
+ }
844
+
845
+ // Test all post types bound to the term. Only if all post types are excluded, set this option.
846
+ $_post_type_noarchive_set = [];
847
+ foreach ( $this->query_cache['states']['posttypes'] as $_post_type ) {
848
+ $_post_type_noarchive_set[] = isset( $robots_global['posttype']['noarchive'][ $_post_type ] );
849
+ }
850
+ if ( ! in_array( false, $_post_type_noarchive_set, true ) ) {
851
+ // Status is already set.
852
+ $item['assess']['posttypes'] = $cache['assess']['posttypes'];
853
+ }
854
+
855
+ if ( $robots_global['postcat']['noarchive'] && 'category' === static::$query['taxonomy'] ) {
856
+ // Status is already set.
857
+ $item['assess']['postcats'] = $cache['assess']['postcats'];
858
+ } elseif ( $robots_global['posttag']['noarchive'] && 'post_tag' === static::$query['taxonomy'] ) {
859
+ // Status is already set.
860
+ $item['assess']['posttags'] = $cache['assess']['posttags'];
861
+ }
862
+
863
+ if ( 0 !== static::$tsf->s_qubit( $this->query_cache['meta']['noarchive'] ) ) {
864
+ // Status is already set.
865
+
866
+ // Don't assert posttype nor site as "blocking" if there's an overide.
867
+ unset( $item['assess']['site'], $item['assess']['posttypes'], $item['assess']['postcats'], $item['assess']['posttags'] );
868
+
869
+ $item['assess']['override'] = $cache['assess']['override'];
870
+ }
871
+
872
+ if ( ! $this->query_cache['states']['robotsmeta']['noarchive'] ) {
873
+ if ( $this->query_cache['states']['robotsmeta']['noindex'] ) {
874
+ $item['status'] = \The_SEO_Framework\Interpreters\SeoBar::STATE_OKAY;
875
+ $item['assess']['noindex'] = $cache['assess']['noindex'];
876
+ }
877
+
878
+ if ( $robots_global['hasrobotstxt'] ) {
879
+ // Don't change status, we do not parse the robots.txt file. Merely disclaim.
880
+ $item['assess']['robotstxt'] = $cache['assess']['robotstxt'];
881
+ }
882
+ }
883
+
884
+ return $item;
885
+ }
886
+
887
+ /**
888
+ * Runs redirect tests.
889
+ *
890
+ * @since 4.0.0
891
+ * @see test_title() for return value.
892
+ *
893
+ * @return array $item
894
+ */
895
+ protected function test_redirect() {
896
+ if ( empty( $this->query_cache['meta']['redirect'] ) ) {
897
+ return static::get_cache( 'term/redirect/default/0' ) ?: static::set_cache(
898
+ 'term/redirect/default/0',
899
+ [
900
+ 'symbol' => \_x( 'R', 'Redirect', 'autodescription' ),
901
+ 'title' => \__( 'Redirection', 'autodescription' ),
902
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_GOOD,
903
+ 'reason' => \__( 'Term does not redirect visitors.', 'autodescription' ),
904
+ 'assess' => [
905
+ 'redirect' => \__( 'All visitors and crawlers may access this page.', 'autodescription' ),
906
+ ],
907
+ 'meta' => [
908
+ 'blocking' => false,
909
+ ],
910
+ ]
911
+ );
912
+ } else {
913
+ return static::get_cache( 'term/redirect/default/1' ) ?: static::set_cache(
914
+ 'term/redirect/default/1',
915
+ [
916
+ 'symbol' => \_x( 'R', 'Redirect', 'autodescription' ),
917
+ 'title' => \__( 'Redirection', 'autodescription' ),
918
+ 'status' => \The_SEO_Framework\Interpreters\SeoBar::STATE_UNKNOWN,
919
+ 'reason' => \__( 'Term redirects visitors.', 'autodescription' ),
920
+ 'assess' => [
921
+ 'redirect' => \__( 'All visitors and crawlers are being redirected. So, no other SEO enhancements are effective.', 'autodescription' ),
922
+ ],
923
+ 'meta' => [
924
+ 'blocking' => true,
925
+ ],
926
+ ]
927
+ );
928
+ }
929
+ }
930
+ }
inc/classes/builders/seobar.class.php ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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 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
+ use \The_SEO_Framework\Traits\Enclose_Core_Final;
44
+
45
+ /**
46
+ * @since 4.0.0
47
+ * @abstract
48
+ * Shared between instances. But, should be overwritten.
49
+ * @var array All known tests.
50
+ */
51
+ public static $tests = [];
52
+
53
+ /**
54
+ * @since 4.0.0
55
+ * Shared between instances.
56
+ * @var null|\The_SEO_Framework\Load
57
+ */
58
+ protected static $tsf = null;
59
+
60
+ /**
61
+ * @since 4.0.0
62
+ * Shared between instances.
63
+ * @var array $cache A non-volatile caching status. Holds post type settings,
64
+ * among other things, to be used in generation.
65
+ */
66
+ private static $cache = [];
67
+
68
+ /**
69
+ * @since 4.0.0
70
+ * Not shared between instances.
71
+ * @var array $query The current query for the SEO Bar.
72
+ */
73
+ protected static $query;
74
+
75
+ /**
76
+ * @since 4.0.0
77
+ * @var arrray The current query cache.
78
+ */
79
+ protected $query_cache = [];
80
+
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
+
88
+ /**
89
+ * Constructor.
90
+ *
91
+ * Sets late static binding.
92
+ *
93
+ * @since 4.0.0
94
+ */
95
+ final protected function __construct() {
96
+ static::$instance = &$this;
97
+ self::$tsf = self::$tsf ?: \the_seo_framework();
98
+ $this->prime_cache();
99
+ }
100
+
101
+ /**
102
+ * Returns this instance.
103
+ *
104
+ * @since 4.0.0
105
+ *
106
+ * @return static
107
+ */
108
+ final public static function get_instance() {
109
+ static::$instance instanceof static or new static;
110
+ return static::$instance;
111
+ }
112
+
113
+ /**
114
+ * Sets non-volatile cache by key value.
115
+ * This cache will stick around for multiple SEO Bar generations.
116
+ *
117
+ * @since 4.0.0
118
+ *
119
+ * @param string $key The cache key.
120
+ * @param mixed $value The cache value.
121
+ * @return mixed The cache value.
122
+ */
123
+ final protected static function set_cache( $key, $value ) {
124
+ return self::$cache[ $key ] = $value;
125
+ }
126
+
127
+ /**
128
+ * Retrieves non-volatile cache value by key.
129
+ * This cache will stick around for multiple SEO Bar generations.
130
+ *
131
+ * @since 4.0.0
132
+ *
133
+ * @param string $key The cache key.
134
+ * @return mixed|null The cache value. Null on failure.
135
+ */
136
+ final protected static function get_cache( $key ) {
137
+ return isset( self::$cache[ $key ] ) ? self::$cache[ $key ] : null;
138
+ }
139
+
140
+ /**
141
+ * Runs all SEO bar tests.
142
+ *
143
+ * @since ?.?.?
144
+ * @access private
145
+ * @generator
146
+ * @TODO only available from PHP 7+
147
+ * @ignore
148
+ *
149
+ * @param array $query : {
150
+ * int $id : Required. The current post or term ID.
151
+ * string $taxonomy : Optional. If not set, this will interpret it as a post.
152
+ * string $post_type : Optional. If not set, this will be automatically filled.
153
+ * This parameter is ignored for taxonomies.
154
+ * }
155
+ * @yield array : {
156
+ * string $test => array The testing results.
157
+ * }
158
+ */
159
+ // phpcs:disable, Squiz.PHP.CommentedOutCode -- Ignore. PHP 7.0+
160
+ // public static function _run_all_tests( array $query ) {
161
+ // yield from static::_run_test( static::$tests, $query );
162
+ // }
163
+ // phpcs:enable, Squiz.PHP.CommentedOutCode
164
+
165
+ /**
166
+ * Runs one or more SEO bar tests.
167
+ *
168
+ * @since 4.0.0
169
+ * @access private
170
+ * @generator
171
+ *
172
+ * @param array|string $tests The test(s) to perform.
173
+ * @param array $query : {
174
+ * int $id : Required. The current post or term ID.
175
+ * string $taxonomy : Optional. If not set, this will interpret it as a post.
176
+ * string $post_type : Optional. If not set, this will be automatically filled.
177
+ * This parameter is ignored for taxonomies.
178
+ * }
179
+ * @yield array : {
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
+
187
+ static::$query = $query;
188
+
189
+ $this->prime_query_cache( $this->query_cache );
190
+
191
+ if ( in_array( 'redirect', $tests, true ) && $this->has_blocking_redirect() )
192
+ $tests = [ 'redirect' ];
193
+
194
+ foreach ( $tests as $test )
195
+ yield $test => $this->{"test_$test"}();
196
+
197
+ $this->query_cache = [];
198
+ }
199
+
200
+ /**
201
+ * Primes the cache.
202
+ *
203
+ * @since 4.0.0
204
+ * @abstract
205
+ */
206
+ abstract protected function prime_cache();
207
+
208
+ /**
209
+ * Primes the current query cache.
210
+ *
211
+ * @since 4.0.0
212
+ * @abstract
213
+ *
214
+ * @param array $query_cache The current query cache. Passed by reference.
215
+ */
216
+ abstract protected function prime_query_cache( array &$query_cache = [] );
217
+
218
+ /**
219
+ * Tests for blocking redirection.
220
+ *
221
+ * @since 4.0.0
222
+ * @abstract
223
+ *
224
+ * @return bool True if there's a blocking redirect, false otherwise.
225
+ */
226
+ abstract protected function has_blocking_redirect();
227
+ }
inc/classes/builders/sitemap-base.class.php ADDED
@@ -0,0 +1,484 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Classes\Builders\Sitemap\Base
4
+ * @subpackage The_SEO_Framework\Sitemap
5
+ */
6
+
7
+ namespace The_SEO_Framework\Builders;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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 base sitemap.
30
+ *
31
+ * @since 4.0.0
32
+ *
33
+ * @access private
34
+ */
35
+ class Sitemap_Base extends Sitemap {
36
+
37
+ /**
38
+ * Generate sitemap.xml content.
39
+ *
40
+ * @since 2.2.9
41
+ * @since 2.8.0 Now adjusts memory limit when possible.
42
+ * @since 2.9.3 No longer crashes on WordPress sites below WP 4.6.
43
+ * @since 3.0.4 No longer outputs empty URL entries.
44
+ * @since 3.1.0 1. Removed the WP<4.6 function_exists check.
45
+ * 2. Now uses WordPress' built-in memory raiser function, with "context" sitemap.
46
+ * @since 4.0.0 1. Now assesses all public post types, in favor of qubit options.
47
+ * 2. Improved performance by a factor of two+.
48
+ * 3. Renamed method from "generate_sitemap" to abstract extension "build_sitemap".
49
+ * 4. Moved to \The_SEO_Framework\Builders\Sitemap_Base
50
+ * @abstract
51
+ *
52
+ * @return string The sitemap content.
53
+ */
54
+ public function build_sitemap() {
55
+
56
+ $content = '';
57
+ $count = 0;
58
+
59
+ $show_priority = (bool) static::$tsf->get_option( 'sitemaps_priority' );
60
+ $show_modified = (bool) static::$tsf->get_option( 'sitemaps_modified' );
61
+
62
+ /**
63
+ * @since 2.2.9
64
+ * @param bool $timestamp Whether to display the timestamp.
65
+ */
66
+ $timestamp = (bool) \apply_filters( 'the_seo_framework_sitemap_timestamp', true );
67
+
68
+ if ( $timestamp )
69
+ $content .= sprintf(
70
+ '<!-- %s -->',
71
+ sprintf(
72
+ /* translators: %s = timestamp */
73
+ \esc_html__( 'Sitemap is generated on %s', 'autodescription' ),
74
+ \current_time( 'Y-m-d H:i:s \G\M\T' )
75
+ )
76
+ ) . "\n";
77
+
78
+ foreach ( $this->generate_front_and_blog_url_items(
79
+ compact( 'show_priority', 'show_modified' ),
80
+ $count
81
+ ) as $_values )
82
+ $content .= $this->build_url_item( $_values );
83
+
84
+ $post_types = array_diff( static::$tsf->get_supported_post_types(), [ 'attachment' ] );
85
+
86
+ /**
87
+ * @since 4.0.0
88
+ * @param array $post_types The supported post types.
89
+ */
90
+ $post_types = (array) \apply_filters( 'the_seo_framework_sitemap_supported_post_types', $post_types );
91
+
92
+ $non_hierarchical_post_types = [];
93
+ $hierarchical_post_types = [];
94
+
95
+ foreach ( $post_types as $_post_type ) {
96
+ if ( \is_post_type_hierarchical( $_post_type ) ) {
97
+ $hierarchical_post_types[] = $_post_type;
98
+ } else {
99
+ $non_hierarchical_post_types[] = $_post_type;
100
+ }
101
+ }
102
+
103
+ $wp_query = new \WP_Query;
104
+ $wp_query->init();
105
+ $hierarchical_post_ids = $non_hierarchical_post_ids = [];
106
+
107
+ if ( $hierarchical_post_types ) {
108
+ $_exclude_ids = array_filter( [
109
+ (int) \get_option( 'page_on_front' ),
110
+ (int) \get_option( 'page_for_posts' ),
111
+ ] );
112
+
113
+ $_hierarchical_posts_limit = $this->get_sitemap_post_limit( true );
114
+
115
+ /**
116
+ * @since 4.0.0
117
+ * @param array $args The query arguments.
118
+ */
119
+ $_args = \apply_filters(
120
+ 'the_seo_framework_sitemap_hpt_query_args',
121
+ [
122
+ 'posts_per_page' => $_hierarchical_posts_limit + count( $_exclude_ids ),
123
+ 'post_type' => $hierarchical_post_types,
124
+ 'orderby' => 'date',
125
+ 'order' => 'ASC',
126
+ 'post_status' => 'publish',
127
+ 'has_password' => false,
128
+ 'fields' => 'ids',
129
+ 'cache_results' => false,
130
+ 'suppress_filters' => false,
131
+ 'no_found_rows' => true,
132
+ ]
133
+ );
134
+
135
+ $wp_query->query = $wp_query->query_vars = $_args;
136
+
137
+ $hierarchical_post_ids = array_diff( $wp_query->get_posts(), $_exclude_ids );
138
+
139
+ // Stop confusion: trim query to set value (by one or two, depending on whether the homepage and blog are included).
140
+ // This is ultimately redundant, but it'll stop support requests by making the input value more accurate.
141
+ if ( count( $hierarchical_post_ids ) > $_hierarchical_posts_limit ) {
142
+ array_splice( $hierarchical_post_ids, $_hierarchical_posts_limit );
143
+ }
144
+ }
145
+
146
+ if ( $non_hierarchical_post_types ) {
147
+ /**
148
+ * @since 4.0.0
149
+ * @param array $args The query arguments.
150
+ */
151
+ $_args = \apply_filters(
152
+ 'the_seo_framework_sitemap_nhpt_query_args',
153
+ [
154
+ 'posts_per_page' => $this->get_sitemap_post_limit( false ),
155
+ 'post_type' => $non_hierarchical_post_types,
156
+ 'orderby' => 'lastmod',
157
+ 'order' => 'DESC',
158
+ 'post_status' => 'publish',
159
+ 'has_password' => false,
160
+ 'fields' => 'ids',
161
+ 'cache_results' => false,
162
+ 'suppress_filters' => false,
163
+ 'no_found_rows' => true,
164
+ ]
165
+ );
166
+
167
+ $wp_query->query = $wp_query->query_vars = $_args;
168
+
169
+ $non_hierarchical_post_ids = $wp_query->get_posts();
170
+ }
171
+
172
+ // Destroy class.
173
+ $wp_query = null;
174
+
175
+ $_items = array_merge( $hierarchical_post_ids, $non_hierarchical_post_ids );
176
+ $total_items = count( $_items );
177
+
178
+ // 49998 = 50000-2, max sitemap items.
179
+ if ( $total_items > 49998 ) array_splice( $_items, 49998 );
180
+
181
+ foreach ( $this->generate_url_item_values(
182
+ $_items,
183
+ compact( 'show_priority', 'show_modified', 'total_items' ),
184
+ $count
185
+ ) as $_values ) {
186
+ $content .= $this->build_url_item( $_values );
187
+ }
188
+
189
+ if ( \has_filter( 'the_seo_framework_sitemap_additional_urls' ) ) {
190
+ foreach ( $this->generate_additional_base_urls(
191
+ compact( 'show_priority', 'show_modified', 'count' ),
192
+ $count
193
+ ) as $_values ) {
194
+ $content .= $this->build_url_item( $_values );
195
+ }
196
+ }
197
+
198
+ /**
199
+ * @since 2.5.2
200
+ * @since 4.0.0 Added $args parameter
201
+ * @param string $extend Custom sitemap extension. Must be escaped.
202
+ * @param array $args : {
203
+ * bool $show_priority : Whether to display priority
204
+ * bool $show_modified : Whether to display modified date.
205
+ * int $total_itemns : Estimate: The total sitemap items before adding additional URLs.
206
+ * }
207
+ */
208
+ $extend = (string) \apply_filters_ref_array(
209
+ 'the_seo_framework_sitemap_extend',
210
+ [
211
+ '',
212
+ compact( 'show_priority', 'show_modified', 'count' ),
213
+ ]
214
+ );
215
+
216
+ if ( $extend )
217
+ $content .= "\t" . $extend . "\n";
218
+
219
+ return $content;
220
+ }
221
+
222
+ /**
223
+ * Generates front-and blog page sitemap items.
224
+ *
225
+ * @since 4.0.0
226
+ * @generator
227
+ *
228
+ * @param array $args The generator arguments.
229
+ * @param int $count The iteration count. Passed by reference.
230
+ * @yield array|void : {
231
+ * string loc
232
+ * string lastmod
233
+ * string priority
234
+ * }
235
+ */
236
+ protected function generate_front_and_blog_url_items( $args, &$count = 0 ) {
237
+
238
+ $front_page_id = (int) \get_option( 'page_on_front' );
239
+ $posts_page_id = (int) \get_option( 'page_for_posts' );
240
+
241
+ if ( static::$tsf->has_page_on_front() ) {
242
+ if ( $front_page_id && $this->is_post_included_in_sitemap( $front_page_id ) ) {
243
+ // TODO use this instead @ PHP7
244
+ // yield from $this->generate_url_item_values()
245
+
246
+ // Reset.
247
+ $_values = [];
248
+ $_values['loc'] = static::$tsf->create_canonical_url( [ 'id' => $front_page_id ] );
249
+
250
+ if ( $args['show_modified'] ) {
251
+ $post = \get_post( $front_page_id );
252
+ $_values['lastmod'] = isset( $post->post_modified_gmt ) ? $post->post_modified_gmt : false;
253
+ }
254
+
255
+ if ( $args['show_priority'] ) {
256
+ $_values['priority'] = '1.0';
257
+ }
258
+
259
+ ++$count;
260
+ yield $_values;
261
+ }
262
+ if ( $posts_page_id && $this->is_post_included_in_sitemap( $posts_page_id ) ) {
263
+ // Reset.
264
+ $_values = [];
265
+ $_values['loc'] = static::$tsf->create_canonical_url( [ 'id' => $posts_page_id ] );
266
+
267
+ if ( $args['show_modified'] ) {
268
+ $latests_posts = \wp_get_recent_posts(
269
+ [
270
+ 'numberposts' => 1,
271
+ 'post_type' => 'post',
272
+ 'post_status' => 'publish',
273
+ 'has_password' => false,
274
+ 'orderby' => 'post_date',
275
+ 'order' => 'DESC',
276
+ 'offset' => 0,
277
+ ],
278
+ OBJECT
279
+ );
280
+ $latest_post = isset( $latests_posts[0] ) ? $latests_posts[0] : null;
281
+ $_publish_post = isset( $latest_post->post_date_gmt ) ? $latest_post->post_date_gmt : '0000-00-00 00:00:00';
282
+
283
+ $post = \get_post( $posts_page_id );
284
+ $_lastmod_blog = isset( $post->post_modified_gmt ) ? $post->post_modified_gmt : '0000-00-00 00:00:00';
285
+
286
+ if ( strtotime( $_publish_post ) > strtotime( $_lastmod_blog ) ) {
287
+ $_values['lastmod'] = $_publish_post;
288
+ } else {
289
+ $_values['lastmod'] = $_lastmod_blog;
290
+ }
291
+ }
292
+
293
+ if ( $args['show_priority'] ) {
294
+ $_values['priority'] = '1.0';
295
+ }
296
+
297
+ ++$count;
298
+ yield $_values;
299
+ }
300
+ } else {
301
+ // Blog page as front.
302
+ if ( $this->is_post_included_in_sitemap( 0 ) ) {
303
+ // Reset.
304
+ $_values = [];
305
+ $_values['loc'] = static::$tsf->get_homepage_permalink();
306
+
307
+ if ( $args['show_modified'] ) {
308
+ $latests_posts = \wp_get_recent_posts(
309
+ [
310
+ 'numberposts' => 1,
311
+ 'post_type' => 'post',
312
+ 'post_status' => 'publish',
313
+ 'has_password' => false,
314
+ 'orderby' => 'post_date',
315
+ 'order' => 'DESC',
316
+ 'offset' => 0,
317
+ ],
318
+ OBJECT
319
+ );
320
+ $latest_post = isset( $latests_posts[0] ) ? $latests_posts[0] : null;
321
+ $_publish_post = isset( $latest_post->post_date_gmt ) ? $latest_post->post_date_gmt : '0000-00-00 00:00:00';
322
+
323
+ $_values['lastmod'] = $_publish_post;
324
+ }
325
+
326
+ if ( $args['show_priority'] ) {
327
+ $_values['priority'] = '1.0';
328
+ }
329
+
330
+ ++$count;
331
+ yield $_values;
332
+ }
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Generates front-and blog page sitemap URL item values.
338
+ *
339
+ * @since 4.0.0
340
+ * @generator
341
+ * @iterator
342
+ *
343
+ * @param iterable $post_ids The post IDs to go over.
344
+ * @param array $args The generator arguments.
345
+ * @param int $count The iteration count. Passed by reference.
346
+ * @yield array|void : {
347
+ * string loc
348
+ * string lastmod
349
+ * string priority
350
+ * }
351
+ */
352
+ protected function generate_url_item_values( $post_ids, $args, &$count = 0 ) {
353
+
354
+ foreach ( $post_ids as $post_id ) {
355
+ if ( $this->is_post_included_in_sitemap( $post_id ) ) {
356
+ $_values = [];
357
+ $_values['loc'] = static::$tsf->create_canonical_url( [ 'id' => $post_id ] );
358
+
359
+ if ( $args['show_modified'] ) {
360
+ $post = \get_post( $post_id );
361
+
362
+ $_values['lastmod'] = isset( $post->post_modified_gmt ) ? $post->post_modified_gmt : '0000-00-00 00:00:00';
363
+ }
364
+
365
+ if ( $args['show_priority'] ) {
366
+ // Add at least 1 to prevent going negative. We add 9 to smoothen the slope.
367
+ $_values['priority'] = .949999 - ( $count / ( $args['total_items'] + 9 ) );
368
+ }
369
+
370
+ ++$count;
371
+ yield $_values;
372
+ }
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Builds and returns a sitemap URL item.
378
+ *
379
+ * @since 4.0.0
380
+ * @staticvar string $timestamp_format
381
+ *
382
+ * @param array $args : {
383
+ * string $loc : The item's URI.
384
+ * string|void|false $lastmod : string if set and not '0000-00-00 00:00:00', false otherwise.
385
+ * int|float|void|false $priority : int if set, false otherwise.
386
+ * }
387
+ * @return string The sitemap item.
388
+ */
389
+ protected function build_url_item( $args ) {
390
+
391
+ if ( empty( $args['loc'] ) ) return '';
392
+
393
+ static $timestamp_format = null;
394
+
395
+ $timestamp_format = $timestamp_format ?: static::$tsf->get_timestamp_format();
396
+
397
+ return sprintf(
398
+ "\t<url>\n%s\t</url>\n",
399
+ vsprintf(
400
+ '%s%s%s',
401
+ [
402
+ sprintf(
403
+ "\t\t<loc>%s</loc>\n",
404
+ $args['loc'] // Already escaped.
405
+ ),
406
+ isset( $args['lastmod'] ) && '0000-00-00 00:00:00' !== $args['lastmod']
407
+ ? sprintf( "\t\t<lastmod>%s</lastmod>\n", static::$tsf->gmt2date( $timestamp_format, $args['lastmod'] ) )
408
+ : '',
409
+ isset( $args['priority'] ) && is_numeric( $args['priority'] )
410
+ ? sprintf( "\t\t<priority>%s</priority>\n", number_format( $args['priority'], 1, '.', ',' ) )
411
+ : '',
412
+ ]
413
+ )
414
+ );
415
+ }
416
+
417
+ /**
418
+ * Retrieves additional URLs and builds items from them.
419
+ *
420
+ * @since 4.0.0
421
+ * @since 4.0.1: 1. Converted to generator and iterator. Therefore, renamed function.
422
+ * 2. Now actually does something.
423
+ * @generator
424
+ * @iterator
425
+ *
426
+ * @param array $args : {
427
+ * bool $show_priority : Whether to display priority
428
+ * bool $show_modified : Whether to display modified date.
429
+ * int $count : The total sitemap items before adding additional URLs.
430
+ * }
431
+ * @param int $count The iteration count. Passed by reference.
432
+ * @yield array|void : {
433
+ * string loc
434
+ * string lastmod
435
+ * string priority
436
+ * }
437
+ */
438
+ protected function generate_additional_base_urls( $args, &$count = 0 ) {
439
+
440
+ /**
441
+ * @since 2.5.2
442
+ * @since 3.2.2 Invalid URLs are now skipped.
443
+ * @since 4.0.0 Added $args parameter.
444
+ * @example return value: [ 'http://example.com' => [ 'lastmod' => '14-01-2018', 'priority' => 0.9 ] ]
445
+ * @param array $custom_urls : {
446
+ * string (key) $url The absolute url to the page. : array {
447
+ * string $lastmod : UNIXTIME <GMT+0> Last modified date, e.g. "2016-01-26 13:04:55"
448
+ * float|int|string $priority : URL Priority
449
+ * }
450
+ * }
451
+ * @param array $args : {
452
+ * bool $show_priority : Whether to display priority
453
+ * bool $show_modified : Whether to display modified date.
454
+ * int $count : Estimate: The total sitemap items before adding additional URLs.
455
+ * }
456
+ */
457
+ $custom_urls = (array) \apply_filters( 'the_seo_framework_sitemap_additional_urls', [], $args );
458
+
459
+ foreach ( $custom_urls as $url => $values ) {
460
+ if ( ! is_array( $values ) ) {
461
+ //* If there are no args, it's assigned as URL (per example)
462
+ $url = $values;
463
+ }
464
+
465
+ // Test if URL is valid.
466
+ if ( ! \esc_url_raw( $url, [ 'https', 'http' ] ) ) continue;
467
+
468
+ // Reset.
469
+ $_values = [];
470
+ $_values['loc'] = $url;
471
+
472
+ if ( $args['show_modified'] ) {
473
+ $_values['lastmod'] = ! empty( $values['lastmod'] ) ? $values['lastmod'] : '0000-00-00 00:00:00';
474
+ }
475
+
476
+ if ( $args['show_priority'] ) {
477
+ $_values['priority'] = ! empty( $values['priority'] ) ? $values['priority'] : 0.9;
478
+ }
479
+
480
+ ++$count;
481
+ yield $_values;
482
+ }
483
+ }
484
+ }
inc/classes/builders/sitemap.class.php ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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
+ * @abstract
33
+ *
34
+ * @access public
35
+ */
36
+ abstract class Sitemap {
37
+ use \The_SEO_Framework\Traits\Enclose_Core_Final;
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 = \the_seo_framework();
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
+ */
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();
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
+ * Returns the sitemap content.
86
+ *
87
+ * @since 4.0.0
88
+ * @abstract
89
+ *
90
+ * @return string The sitemap content.
91
+ */
92
+ abstract public function build_sitemap();
93
+
94
+ /**
95
+ * Determines if post is possibly included in the sitemap.
96
+ *
97
+ * This is a weak check, as the filter might not be present outside of the sitemap's scope.
98
+ * The URL also isn't checked, nor the position.
99
+ *
100
+ * @since 3.0.4
101
+ * @since 3.0.6 First filter value now works as intended.
102
+ * @since 3.1.0 1. Resolved a PHP notice when ID is 0, resulting in returning false-esque unintentionally.
103
+ * 2. Now accepts 0 in the filter.
104
+ * @since 4.0.0 1. Now tests qubit options.
105
+ * 2. Now tests for redirect settings.
106
+ * 3. First parameter can now be a post object.
107
+ * 4. If the first parameter is 0, it's now indicative of a home-as-blog page.
108
+ * 5. Moved to \The_SEO_Framework\Builders\Sitemap
109
+ *
110
+ * @param int $post_id The Post ID to check.
111
+ * @return bool True if included, false otherwise.
112
+ */
113
+ final public function is_post_included_in_sitemap( $post_id ) {
114
+
115
+ static $excluded = null;
116
+ if ( null === $excluded ) {
117
+ /**
118
+ * @since 2.5.2
119
+ * @since 2.8.0 : No longer accepts '0' as entry.
120
+ * @since 3.1.0 : '0' is accepted again.
121
+ * @param array $excluded Sequential list of excluded IDs: [ int ...post_id ]
122
+ */
123
+ $excluded = (array) \apply_filters( 'the_seo_framework_sitemap_exclude_ids', [] );
124
+
125
+ if ( empty( $excluded ) ) {
126
+ $excluded = [];
127
+ } else {
128
+ $excluded = array_flip( $excluded );
129
+ }
130
+ }
131
+
132
+ // ROBOTS_IGNORE_PROTECTION as we don't need to test 'private' (because of sole 'publish'), and 'password' (because of false 'has_password')
133
+ return ! isset( $excluded[ $post_id ] )
134
+ && ! static::$tsf->is_robots_meta_noindex_set_by_args( [ 'id' => $post_id ], \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION );
135
+ }
136
+
137
+ /**
138
+ * Determines if post is possibly included in the sitemap.
139
+ *
140
+ * This is a weak check, as the filter might not be present outside of the sitemap's scope.
141
+ * The URL also isn't checked, nor the position.
142
+ *
143
+ * @since 4.0.0
144
+ * @see https://github.com/sybrew/tsf-term-sitemap for example.
145
+ *
146
+ * @param int $term_id The Term ID to check.
147
+ * @param string $taxonomy The taxonomy.
148
+ * @return bool True if included, false otherwise.
149
+ */
150
+ final public function is_term_included_in_sitemap( $term_id, $taxonomy ) {
151
+
152
+ static $excluded = null;
153
+ if ( null === $excluded ) {
154
+ /**
155
+ * @since 4.0.0
156
+ * @ignore NOT USED INTERNALLY!
157
+ * @param array $excluded Sequential list of excluded IDs: [ int ...term_id ]
158
+ */
159
+ $excluded = (array) \apply_filters( 'the_seo_framework_sitemap_exclude_term_ids', [] );
160
+
161
+ if ( empty( $excluded ) ) {
162
+ $excluded = [];
163
+ } else {
164
+ $excluded = array_flip( $excluded );
165
+ }
166
+ }
167
+
168
+ // ROBOTS_IGNORE_PROTECTION is not tested for terms. However, we may use that later.
169
+ return ! isset( $excluded[ $term_id ] )
170
+ && ! static::$tsf->is_robots_meta_noindex_set_by_args(
171
+ [
172
+ 'id' => $term_id,
173
+ 'taxonomy' => $taxonomy,
174
+ ],
175
+ \The_SEO_Framework\ROBOTS_IGNORE_PROTECTION
176
+ );
177
+ }
178
+
179
+ /**
180
+ * Returns the sitemap post query limit.
181
+ *
182
+ * @since 3.1.0
183
+ * @since 4.0.0 Moved to \The_SEO_Framework\Builders\Sitemap
184
+ *
185
+ * @param bool $hierarchical Whether the query is for hierarchical post types or not.
186
+ * @return int The post limit
187
+ */
188
+ final protected function get_sitemap_post_limit( $hierarchical = false ) {
189
+ /**
190
+ * @since 2.2.9
191
+ * @since 2.8.0 Increased to 1200 from 700.
192
+ * @since 3.1.0 Now returns an option value; it falls back to the default value if not set.
193
+ * @since 4.0.0 1. The default is now 3000, from 1200.
194
+ * 2. Now passes a second parameter.
195
+ * @param int $total_post_limit
196
+ * @param bool $hierarchical Whether the query is for hierarchical post types or not.
197
+ */
198
+ return (int) \apply_filters(
199
+ 'the_seo_framework_sitemap_post_limit',
200
+ static::$tsf->get_option( 'sitemap_query_limit' ),
201
+ $hierarchical
202
+ );
203
+ }
204
+ }
inc/classes/cache.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -30,7 +32,7 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
30
  *
31
  * @since 2.8.0
32
  */
33
- class Cache extends Sitemaps {
34
 
35
  /**
36
  * Determines whether object cache is being used.
@@ -129,8 +131,7 @@ class Cache extends Sitemaps {
129
  */
130
  public function delete_post_cache( $post_id ) {
131
 
132
- if ( ! $post_id )
133
- return false;
134
 
135
  $success[] = $this->delete_cache( 'post', $post_id );
136
 
@@ -184,13 +185,12 @@ class Cache extends Sitemaps {
184
  * Deletes object cache.
185
  *
186
  * @since 2.9.0
187
- * @TODO make this work.
188
  *
189
  * @return bool True on success, false on failure.
190
  */
191
  public function delete_object_cache() {
192
- return false;
193
- // return $this->delete_cache( 'objectflush' );
194
  }
195
 
196
  /**
@@ -275,27 +275,6 @@ class Cache extends Sitemaps {
275
  case 'detection':
276
  break;
277
 
278
- /**
279
- * Flush whole object cache group.
280
- * Set here for external functions to use. It works because of magic methods.
281
- *
282
- * @NOTE Other caching plugins can override these groups. Therefore this
283
- * does NOT work.
284
- * @TODO make this work.
285
- * @see 'object' switch-index.
286
- */
287
- case 'objectflush':
288
- //* @NOTE false can't pass.
289
- if ( false && $this->use_object_cache ) {
290
- if ( isset( $GLOBALS['wp_object_cache']->cache['the_seo_framework'] ) ) {
291
- $_cache = $GLOBALS['wp_object_cache']->cache;
292
- unset( $_cache['the_seo_framework'] );
293
- $GLOBALS['wp_object_cache']->cache = $_cache;
294
- $success = true;
295
- }
296
- }
297
- break;
298
-
299
  default:
300
  break;
301
  endswitch;
@@ -303,8 +282,8 @@ class Cache extends Sitemaps {
303
  /**
304
  * @since 3.1.0
305
  *
306
- * @param string $type The type. Comes in handy when you use a catch-all function.
307
- * @param int $id The post, page or TT ID. Defaults to $this->get_the_real_ID().
308
  * @param array $args Additional arguments. They can overwrite $type and $id.
309
  * @param bool $success Whether the action cleared.
310
  */
@@ -352,13 +331,13 @@ class Cache extends Sitemaps {
352
  * Set the value of the transient.
353
  *
354
  * Prevents setting of transients when they're disabled.
355
- * @see $this->the_seo_framework_use_transients
356
  *
357
  * @since 2.6.0
 
358
  *
359
- * @param string $transient Transient name. Expected to not be SQL-escaped.
360
- * @param string $value Transient value. Expected to not be SQL-escaped.
361
- * @param int $expiration Optional Transient expiration date, optional. Expected to not be SQL-escaped.
362
  */
363
  public function set_transient( $transient, $value, $expiration = 0 ) {
364
 
@@ -372,9 +351,9 @@ class Cache extends Sitemaps {
372
  * If the transient does not exists, does not have a value or has expired,
373
  * or transients have been disabled through a constant, then the transient
374
  * will be false.
375
- * @see $this->the_seo_framework_use_transients
376
  *
377
  * @since 2.6.0
 
378
  *
379
  * @param string $transient Transient name. Expected to not be SQL-escaped.
380
  * @return mixed|bool Value of the transient. False on failure or non existing transient.
@@ -550,7 +529,7 @@ class Cache extends Sitemaps {
550
 
551
  //* Placeholder ID.
552
  $the_id = '';
553
- $_t = $taxonomy;
554
 
555
  if ( $this->is_404() ) {
556
  $the_id = '_404_';
@@ -626,7 +605,7 @@ class Cache extends Sitemaps {
626
 
627
  $the_id = 'archives_' . $the_id;
628
  }
629
- } elseif ( ( $this->is_real_front_page() || $this->is_front_page_by_id( $page_id ) ) || ( $this->is_admin() && $this->is_seo_settings_page( true ) ) ) {
630
  //* Front/HomePage.
631
  $the_id = $this->generate_front_page_cache_key();
632
  } elseif ( $this->is_blog_page( $page_id ) ) {
@@ -653,26 +632,13 @@ class Cache extends Sitemaps {
653
  break;
654
  endswitch;
655
  } elseif ( $this->is_search() ) {
656
- $query = '';
657
-
658
- //* TODO figure out why this check is here... admin compat maybe?
659
- //! TODO convert the search query to a hash: search_(hash)... encode first!
660
- if ( function_exists( 'get_search_query' ) ) {
661
- $search_query = \get_search_query( $_escaped = true );
662
-
663
- if ( $search_query )
664
- $query = str_replace( ' ', '', $search_query );
665
-
666
- //* Limit to 10 chars.
667
- if ( mb_strlen( $query ) > 10 )
668
- $query = mb_substr( $query, 0, 10 );
669
-
670
- $query = \esc_sql( $query );
671
- }
672
 
673
  //* Temporarily disable caches to prevent database spam.
674
  $this->the_seo_framework_use_transients = false;
675
- $this->use_object_cache = false;
676
 
677
  $the_id = $page_id . '_s_' . $query;
678
  }
@@ -732,6 +698,8 @@ class Cache extends Sitemaps {
732
  case 'term':
733
  return $this->add_cache_key_suffix( $this->generate_taxonomical_cache_key( $page_id, $taxonomy ) );
734
  break;
 
 
735
  default:
736
  $this->_doing_it_wrong( __METHOD__, 'Third parameter must be a known type.', '2.6.5' );
737
  return $this->add_cache_key_suffix( \esc_sql( $type . '_' . $page_id . '_' . $taxonomy ) );
@@ -745,21 +713,16 @@ class Cache extends Sitemaps {
745
  * Adds cache key suffix based on blog id and locale.
746
  *
747
  * @since 2.7.0
748
- * @since 2.8.0 1: $locale is now static.
749
- * 2: $key may now be empty.
750
- * @staticvar string $locale
751
  * @global string $blog_id
752
  *
753
- * @return string the cache key.
 
754
  */
755
  protected function add_cache_key_suffix( $key = '' ) {
756
-
757
- static $locale = null;
758
-
759
- if ( is_null( $locale ) )
760
- $locale = strtolower( \get_locale() );
761
-
762
- return $key . '_' . $GLOBALS['blog_id'] . '_' . $locale;
763
  }
764
 
765
  /**
@@ -772,17 +735,15 @@ class Cache extends Sitemaps {
772
  */
773
  public function generate_front_page_cache_key( $type = '' ) {
774
 
775
- if ( empty( $type ) ) {
776
  if ( $this->has_page_on_front() ) {
777
  $type = 'page';
778
  } else {
779
  $type = 'blog';
780
  }
781
- } else {
782
- $type = \esc_sql( $type );
783
  }
784
 
785
- return $the_id = 'h' . $type . '_' . $this->get_the_front_page_ID();
786
  }
787
 
788
  /**
@@ -811,7 +772,7 @@ class Cache extends Sitemaps {
811
  }
812
  }
813
 
814
- if ( empty( $the_id ) ) {
815
  if ( mb_strlen( $taxonomy ) >= 5 ) {
816
  $the_id = mb_substr( $taxonomy, 0, 5 );
817
  } else {
@@ -836,7 +797,7 @@ class Cache extends Sitemaps {
836
 
837
  $revision = '1';
838
 
839
- return $cache_key = 'robots_txt_output_' . $revision . $GLOBALS['blog_id'];
840
  }
841
 
842
  /**
@@ -857,7 +818,7 @@ class Cache extends Sitemaps {
857
  $page = (string) $this->page();
858
  $paged = (string) $this->paged();
859
 
860
- return $cache_key = 'seo_framework_output_' . $key . '_' . $paged . '_' . $page;
861
  }
862
 
863
  /**
@@ -883,7 +844,7 @@ class Cache extends Sitemaps {
883
  //= Refers to the first page, always.
884
  $_page = $_paged = '1';
885
 
886
- return $cache_key = 'seo_framework_output_' . $key . '_' . $_paged . '_' . $_page;
887
  }
888
 
889
  /**
@@ -925,7 +886,12 @@ class Cache extends Sitemaps {
925
 
926
  $transient = $this->get_sitemap_transient_name();
927
  $transient and \delete_transient( $transient );
928
- $this->ping_searchengines();
 
 
 
 
 
929
 
930
  return $run = true;
931
  }
@@ -984,13 +950,16 @@ class Cache extends Sitemaps {
984
  ); // No cache OK, Set in autoloaded transient. DB call ok.
985
 
986
  foreach ( [ 'archive', 'search' ] as $key ) {
987
- array_walk( $cache[ $key ], function( &$v ) {
988
- if ( isset( $v->meta_value, $v->post_id ) && $v->meta_value ) {
989
- $v = (int) $v->post_id;
990
- } else {
991
- $v = false;
 
 
 
992
  }
993
- } );
994
  $cache[ $key ] = array_filter( $cache[ $key ] );
995
  }
996
 
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Cache
4
+ * @subpackage The_SEO_Framework\Cache
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
32
  *
33
  * @since 2.8.0
34
  */
35
+ class Cache extends Site_Options {
36
 
37
  /**
38
  * Determines whether object cache is being used.
131
  */
132
  public function delete_post_cache( $post_id ) {
133
 
134
+ if ( ! $post_id ) return false;
 
135
 
136
  $success[] = $this->delete_cache( 'post', $post_id );
137
 
185
  * Deletes object cache.
186
  *
187
  * @since 2.9.0
188
+ * @since 4.0.0 Now does something.
189
  *
190
  * @return bool True on success, false on failure.
191
  */
192
  public function delete_object_cache() {
193
+ return $this->delete_cache( 'object' );
 
194
  }
195
 
196
  /**
275
  case 'detection':
276
  break;
277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  default:
279
  break;
280
  endswitch;
282
  /**
283
  * @since 3.1.0
284
  *
285
+ * @param string $type The flush type. Comes in handy when you use a catch-all function.
286
+ * @param int $id The post, page or TT ID. Defaults to the_seo_framework()->get_the_real_ID().
287
  * @param array $args Additional arguments. They can overwrite $type and $id.
288
  * @param bool $success Whether the action cleared.
289
  */
331
  * Set the value of the transient.
332
  *
333
  * Prevents setting of transients when they're disabled.
 
334
  *
335
  * @since 2.6.0
336
+ * @uses $this->the_seo_framework_use_transients
337
  *
338
+ * @param string $transient Transient name. Expected to not be SQL-escaped.
339
+ * @param string $value Transient value. Expected to not be SQL-escaped.
340
+ * @param int $expiration Transient expiration date, optional. Expected to not be SQL-escaped.
341
  */
342
  public function set_transient( $transient, $value, $expiration = 0 ) {
343
 
351
  * If the transient does not exists, does not have a value or has expired,
352
  * or transients have been disabled through a constant, then the transient
353
  * will be false.
 
354
  *
355
  * @since 2.6.0
356
+ * @uses $this->the_seo_framework_use_transients
357
  *
358
  * @param string $transient Transient name. Expected to not be SQL-escaped.
359
  * @return mixed|bool Value of the transient. False on failure or non existing transient.
529
 
530
  //* Placeholder ID.
531
  $the_id = '';
532
+ $_t = $taxonomy;
533
 
534
  if ( $this->is_404() ) {
535
  $the_id = '_404_';
605
 
606
  $the_id = 'archives_' . $the_id;
607
  }
608
+ } elseif ( ( $this->is_real_front_page() || $this->is_front_page_by_id( $page_id ) ) || ( \is_admin() && $this->is_seo_settings_page( true ) ) ) {
609
  //* Front/HomePage.
610
  $the_id = $this->generate_front_page_cache_key();
611
  } elseif ( $this->is_blog_page( $page_id ) ) {
632
  break;
633
  endswitch;
634
  } elseif ( $this->is_search() ) {
635
+ //* Remove spaces. Limit to 10 chars.
636
+ // TODO use metahpone?
637
+ $query = \esc_sql( substr( str_replace( ' ', '', \get_search_query( true ) ), 0, 10 ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
638
 
639
  //* Temporarily disable caches to prevent database spam.
640
  $this->the_seo_framework_use_transients = false;
641
+ $this->use_object_cache = false;
642
 
643
  $the_id = $page_id . '_s_' . $query;
644
  }
698
  case 'term':
699
  return $this->add_cache_key_suffix( $this->generate_taxonomical_cache_key( $page_id, $taxonomy ) );
700
  break;
701
+ case 'ping':
702
+ return $this->add_cache_key_suffix( 'tsf_throttle_ping' );
703
  default:
704
  $this->_doing_it_wrong( __METHOD__, 'Third parameter must be a known type.', '2.6.5' );
705
  return $this->add_cache_key_suffix( \esc_sql( $type . '_' . $page_id . '_' . $taxonomy ) );
713
  * Adds cache key suffix based on blog id and locale.
714
  *
715
  * @since 2.7.0
716
+ * @since 2.8.0 1. $locale is now static.
717
+ * 2. $key may now be empty.
718
+ * @since 4.0.0 Removed caching, so to support translation plugin loops.
719
  * @global string $blog_id
720
  *
721
+ * @param string $key The cache key.
722
+ * @return string
723
  */
724
  protected function add_cache_key_suffix( $key = '' ) {
725
+ return $key . '_' . $GLOBALS['blog_id'] . '_' . strtolower( \get_locale() );
 
 
 
 
 
 
726
  }
727
 
728
  /**
735
  */
736
  public function generate_front_page_cache_key( $type = '' ) {
737
 
738
+ if ( ! $type ) {
739
  if ( $this->has_page_on_front() ) {
740
  $type = 'page';
741
  } else {
742
  $type = 'blog';
743
  }
 
 
744
  }
745
 
746
+ return \esc_sql( 'h' . $type . '_' . $this->get_the_front_page_ID() );
747
  }
748
 
749
  /**
772
  }
773
  }
774
 
775
+ if ( ! $the_id ) {
776
  if ( mb_strlen( $taxonomy ) >= 5 ) {
777
  $the_id = mb_substr( $taxonomy, 0, 5 );
778
  } else {
797
 
798
  $revision = '1';
799
 
800
+ return 'robots_txt_output_' . $revision . $GLOBALS['blog_id'];
801
  }
802
 
803
  /**
818
  $page = (string) $this->page();
819
  $paged = (string) $this->paged();
820
 
821
+ return 'seo_framework_output_' . $key . '_' . $paged . '_' . $page;
822
  }
823
 
824
  /**
844
  //= Refers to the first page, always.
845
  $_page = $_paged = '1';
846
 
847
+ return 'seo_framework_output_' . $key . '_' . $_paged . '_' . $_page;
848
  }
849
 
850
  /**
886
 
887
  $transient = $this->get_sitemap_transient_name();
888
  $transient and \delete_transient( $transient );
889
+
890
+ if ( $this->get_option( 'ping_use_cron' ) ) {
891
+ \The_SEO_Framework\Bridges\Ping::engage_pinging_cron();
892
+ } else {
893
+ \The_SEO_Framework\Bridges\Ping::ping_search_engines();
894
+ }
895
 
896
  return $run = true;
897
  }
950
  ); // No cache OK, Set in autoloaded transient. DB call ok.
951
 
952
  foreach ( [ 'archive', 'search' ] as $key ) {
953
+ array_walk(
954
+ $cache[ $key ],
955
+ function( &$v ) {
956
+ if ( isset( $v->meta_value, $v->post_id ) && $v->meta_value ) {
957
+ $v = (int) $v->post_id;
958
+ } else {
959
+ $v = false;
960
+ }
961
  }
962
+ );
963
  $cache[ $key ] = array_filter( $cache[ $key ] );
964
  }
965
 
inc/classes/compat.class.php DELETED
@@ -1,117 +0,0 @@
1
- <?php
2
- /**
3
- * @package The_SEO_Framework\Classes
4
- */
5
- namespace The_SEO_Framework;
6
-
7
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
8
-
9
- /**
10
- * The SEO Framework plugin
11
- * Copyright (C) 2015 - 2019 Sybre Waaijer, CyberWire (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
- /**
27
- * Class The_SEO_Framework\Compat
28
- *
29
- * Adds theme/plugin compatibility.
30
- *
31
- * @since 2.8.0
32
- */
33
- class Compat extends Core {
34
-
35
- /**
36
- * Requires compatibility files which are needed early or on every page.
37
- * Mostly requires premium plugins/themes, so we check actual PHP instances,
38
- * rather than common paths. As they can require manual FTP upload.
39
- *
40
- * @since 2.8.0
41
- * @TODO Add transients that will bypass all these checks.
42
- * Careful, recheck on each activation -- and even FTP deletion.
43
- */
44
- protected function load_early_compat_files() {
45
-
46
- if ( ! extension_loaded( 'mbstring' ) ) {
47
- $this->_include_compat( 'mbstring', 'php' );
48
- }
49
-
50
- // $wp_version = $GLOBALS['wp_version'];
51
-
52
- // if ( version_compare( $wp_version, '4.7', '<' ) ) {
53
- // //* WP 4.7.0
54
- // $this->_include_compat( '470', 'wp' );
55
- // }
56
-
57
- //* Disable Headway SEO.
58
- \add_filter( 'headway_seo_disabled', '__return_true' );
59
-
60
- if ( $this->is_theme( 'genesis' ) ) {
61
- //* Genesis Framework
62
- $this->_include_compat( 'genesis', 'theme' );
63
- }
64
-
65
- if ( $this->detect_plugin( [ 'constants' => [ 'ICL_LANGUAGE_CODE' ] ] ) ) {
66
- //* WPML
67
- $this->_include_compat( 'wpml', 'plugin' );
68
- }
69
- if ( $this->detect_plugin( [ 'globals' => [ 'polylang' ] ] ) ) {
70
- //* Polylang
71
- $this->_include_compat( 'polylang', 'plugin' );
72
- }
73
-
74
- if ( $this->detect_plugin( [ 'globals' => [ 'ultimatemember' ] ] ) ) {
75
- //* Ultimate Member
76
- $this->_include_compat( 'ultimatemember', 'plugin' );
77
- }
78
- if ( $this->detect_plugin( [ 'globals' => [ 'bp' ] ] ) ) {
79
- //* BuddyPress
80
- $this->_include_compat( 'buddypress', 'plugin' );
81
- }
82
-
83
- if ( $this->detect_plugin( [ 'functions' => [ 'bbpress' ] ] ) ) {
84
- //* bbPress
85
- $this->_include_compat( 'bbpress', 'plugin' );
86
- } elseif ( $this->detect_plugin( [ 'constants' => [ 'WPFORO_BASENAME' ] ] ) ) {
87
- //* wpForo
88
- $this->_include_compat( 'wpforo', 'plugin' );
89
- }
90
-
91
- if ( $this->detect_plugin( [ 'functions' => [ 'wc' ] ] ) ) {
92
- //* WooCommerce.
93
- $this->_include_compat( 'woocommerce', 'plugin' );
94
- }
95
- }
96
-
97
- /**
98
- * Includes compatibility files.
99
- *
100
- * @since 2.8.0
101
- * @access private
102
- * @staticvar array $included Maintains cache of whether files have been loaded.
103
- *
104
- * @param string $what The vendor/plugin/theme name for the compatibilty.
105
- * @param string $type The compatibility type. Be it 'plugin' or 'theme'.
106
- * @return bool True on success, false on failure. Files are expected not to return any values.
107
- */
108
- public function _include_compat( $what, $type = 'plugin' ) {
109
-
110
- static $included = [];
111
-
112
- if ( ! isset( $included[ $what ][ $type ] ) )
113
- $included[ $what ][ $type ] = (bool) require THE_SEO_FRAMEWORK_DIR_PATH_COMPAT . $type . '-' . $what . '.php';
114
-
115
- return $included[ $what ][ $type ];
116
- }
117
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/classes/core.class.php CHANGED
@@ -1,8 +1,9 @@
1
  <?php
2
  /**
3
- * @see ./index.php
4
- * @package The_SEO_Framework\Classes
5
  */
 
6
  namespace The_SEO_Framework;
7
 
8
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -32,6 +33,7 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
32
  * @since 2.8.0
33
  */
34
  class Core {
 
35
 
36
  /**
37
  * Tells if this plugin is loaded.
@@ -41,7 +43,7 @@ class Core {
41
  * @since 3.1.0
42
  * @access protected
43
  * Don't alter this variable!!!
44
- * @var true $loaded
45
  */
46
  public $loaded = false;
47
 
@@ -50,16 +52,6 @@ class Core {
50
  */
51
  private function __construct() { }
52
 
53
- /**
54
- * Unserializing instances of this object is forbidden.
55
- */
56
- final protected function __wakeup() { }
57
-
58
- /**
59
- * Cloning of this object is forbidden.
60
- */
61
- final protected function __clone() { }
62
-
63
  /**
64
  * Handles unapproachable invoked properties.
65
  *
@@ -69,8 +61,8 @@ class Core {
69
  * @since 2.8.0
70
  * @since 3.2.2 This method no longer allows to overwrite protected or private variables.
71
  *
72
- * @param string $name The property name.
73
- * @param mixed $value The property value.
74
  */
75
  final public function __set( $name, $value ) {
76
  /**
@@ -97,7 +89,6 @@ class Core {
97
  */
98
  final public function __get( $name ) {
99
  $this->_inaccessible_p_or_m( 'the_seo_framework()->' . $name, 'unknown' );
100
- return;
101
  }
102
 
103
  /**
@@ -105,9 +96,9 @@ class Core {
105
  *
106
  * @since 2.7.0
107
  *
108
- * @param string $name The method name.
109
- * @param array $arguments The method arguments.
110
- * @return void
111
  */
112
  final public function __call( $name, $arguments ) {
113
 
@@ -121,23 +112,23 @@ class Core {
121
  }
122
 
123
  \the_seo_framework()->_inaccessible_p_or_m( 'the_seo_framework()->' . $name . '()' );
124
- return;
125
  }
126
 
127
  /**
128
  * Destroys output buffer, if any. To be used with AJAX and XML to clear any PHP errors or dumps.
129
  *
130
  * @since 2.8.0
131
- * @since 2.9.0 : Now flushes all levels rather than just the latest one.
 
132
  *
133
  * @return bool True on clear. False otherwise.
134
  */
135
- protected function clean_response_header() {
136
 
137
- if ( $level = ob_get_level() ) {
138
- while ( $level-- ) {
139
- ob_end_clean();
140
- }
141
  return true;
142
  }
143
 
@@ -152,9 +143,9 @@ class Core {
152
  * @access private
153
  * @credits Akismet For some code.
154
  *
155
- * @param string $view The file name.
156
- * @param array $args The arguments to be supplied within the file name.
157
- * Each array key is converted to a variable with its value attached.
158
  * @param string $instance The instance suffix to call back upon.
159
  */
160
  public function get_view( $view, array $__args = [], $instance = 'main' ) {
@@ -179,7 +170,7 @@ class Core {
179
  }
180
 
181
  /**
182
- * Fetches view instance for switch.
183
  *
184
  * @since 2.7.0
185
  *
@@ -197,7 +188,7 @@ class Core {
197
  *
198
  * @since 2.6.0
199
  *
200
- * @param int $i The dimension to resize.
201
  * @param int $r1 The deminsion that determines the ratio.
202
  * @param int $r2 The dimension to proportionate to.
203
  * @return int The proportional dimension, rounded.
@@ -206,8 +197,8 @@ class Core {
206
 
207
  //* Get aspect ratio.
208
  $ar = $r1 / $r2;
 
209
 
210
- $i = $i / $ar;
211
  return round( $i );
212
  }
213
 
@@ -231,7 +222,7 @@ class Core {
231
 
232
  $tsf_links['about'] = sprintf(
233
  '<a href="https://theseoframework.com/about-us/" rel="noreferrer noopener nofollow" target="_blank">%s</a>',
234
- \esc_html__( 'About', 'autodescription' )
235
  );
236
  $tsf_links['tsfem'] = sprintf(
237
  '<a href="%s" rel="noreferrer noopener" target="_blank">%s</a>',
@@ -259,29 +250,73 @@ class Core {
259
  if ( THE_SEO_FRAMEWORK_PLUGIN_BASENAME !== $plugin_file )
260
  return $plugin_meta;
261
 
262
- return array_merge( $plugin_meta, [
263
- 'docs' => vsprintf(
264
- '<a href="%s" rel="noreferrer noopener nofollow" target="_blank">%s</a>',
265
- [
266
- 'https://theseoframework.com/?p=80',
267
- \esc_html__( 'View documentation', 'autodescription' ),
268
- ]
269
- ),
270
- 'API' => vsprintf(
271
- '<a href="%s" rel="noreferrer noopener nofollow" target="_blank">%s</a>',
272
- [
273
- 'https://theseoframework.com/?p=82',
274
- \esc_html__( 'View API docs', 'autodescription' ),
275
- ]
276
- ),
277
- 'EM' => vsprintf(
278
- '<a href="%s" rel="noreferrer noopener nofollow" target="_blank">%s</a>',
279
- [
280
- 'https://theseoframework.com/?p=2760',
281
- \esc_html_x( 'Get the Extension Manager', 'Extension Manager is a product name; do not translate', 'autodescription' ),
282
- ]
283
- ),
284
- ] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  }
286
 
287
  /**
@@ -350,25 +385,6 @@ class Core {
350
  return false;
351
  }
352
 
353
- /**
354
- * Whether to lowercase the noun or keep it UCfirst.
355
- * Depending if language is German.
356
- *
357
- * @since 2.6.0
358
- * @staticvar array $lowercase Contains nouns.
359
- *
360
- * @return string The maybe lowercase noun.
361
- */
362
- public function maybe_lowercase_noun( $noun ) {
363
-
364
- static $lowercase = [];
365
-
366
- if ( isset( $lowercase[ $noun ] ) )
367
- return $lowercase[ $noun ];
368
-
369
- return $lowercase[ $noun ] = $this->check_wp_locale( 'de' ) ? $noun : strtolower( $noun );
370
- }
371
-
372
  /**
373
  * Returns the minimum role required to adjust settings.
374
  *
@@ -410,7 +426,7 @@ class Core {
410
 
411
  $url = html_entity_decode( \menu_page_url( $this->seo_settings_page_slug, false ) );
412
 
413
- return \esc_url( $url, [ 'http', 'https' ] );
414
  }
415
 
416
  return '';
@@ -444,22 +460,15 @@ class Core {
444
  * Fetches the Timezone String from given offset.
445
  *
446
  * @since 2.6.0
 
447
  *
448
  * @param int $offset The GMT offzet.
449
  * @return string PHP Timezone String.
450
  */
451
  protected function get_tzstring_from_offset( $offset = 0 ) {
452
 
453
- $seconds = round( $offset * HOUR_IN_SECONDS );
454
-
455
- //* Try Daylight savings.
456
  $tzstring = timezone_name_from_abbr( '', $seconds, 1 );
457
- /**
458
- * PHP bug workaround. Disable the DST check.
459
- * @link https://bugs.php.net/bug.php?id=44780
460
- */
461
- if ( false === $tzstring )
462
- $tzstring = timezone_name_from_abbr( '', $seconds, 0 );
463
 
464
  return $tzstring;
465
  }
@@ -474,8 +483,8 @@ class Core {
474
  * @since 3.0.6 Now uses the old timezone string when a new one can't be generated.
475
  *
476
  * @param string $tzstring Optional. The PHP Timezone string. Best to leave empty to always get a correct one.
477
- * @link http://php.net/manual/en/timezones.php
478
- * @param bool $reset Whether to reset to default. Ignoring first parameter.
479
  * @return bool True on success. False on failure.
480
  */
481
  public function set_timezone( $tzstring = '', $reset = false ) {
@@ -540,7 +549,7 @@ class Core {
540
  * @return string The timestamp format used in PHP date.
541
  */
542
  public function get_timestamp_format() {
543
- return '1' === $this->get_option( 'timestamps_format' ) ? 'Y-m-d\TH:iP' : 'Y-m-d';
544
  }
545
 
546
  /**
@@ -582,57 +591,43 @@ class Core {
582
  * @since 2.7.0
583
  * @since 3.1.0 This method now uses PHP 5.4+ encoding, capable of UTF-8 interpreting,
584
  * instead of relying on PHP's incomplete encoding table.
585
- * This does mean that the functionality is crippled* when the PHP
586
  * installation isn't unicode compatible; this is unlikely.
587
- * @staticvar bool $utf8_pcre Determines whether pcre supports UTF-8.
588
- *
589
- * *Crippled as in skipping every non-latin and diacritic character.
 
590
  *
591
  * @param string $string Required. The string to count words in.
592
- * @param int $amount Minimum amount of words to encounter in the string.
593
- * Set to 0 to count all words longer than $bother_length.
594
- * @param int $amount_bother Minimum amount of words to encounter in the string
595
- * that fall under the $bother_length. Set to 0 to count all words
596
- * shorter than $bother_length.
597
- * @param int $bother_length The maximum string length of a word to pass for
598
- * $amount_bother instead of $amount. Set to 0 to pass all words
599
- * through $amount_bother
600
  * @return array Containing arrays of words with their count.
601
  */
602
- public function get_word_count( $string, $amount = 3, $amount_bother = 5, $bother_length = 3 ) {
603
 
604
  $string = html_entity_decode( $string );
 
605
 
606
- static $utf8_pcre = null;
607
- if ( ! isset( $utf8_pcre ) )
608
- $utf8_pcre = @preg_match( '/^./u', 'a' );
609
-
610
- if ( $utf8_pcre ) {
611
- $string = \wp_check_invalid_utf8( $string, true );
612
- $word_list = preg_split(
613
- '/\W+/mu',
614
- function_exists( 'mb_strtolower' ) ? mb_strtolower( $string ) : strtolower( $string ),
615
- -1,
616
- PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_NO_EMPTY
617
- );
618
- } else {
619
- $word_list = preg_split(
620
- '/\W+/m',
621
- strtolower( $string ),
622
- -1,
623
- PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_NO_EMPTY
624
- );
625
- }
626
 
627
  $words_too_many = [];
628
 
629
  if ( count( $word_list ) ) :
630
- /**
631
- * @since 2.6.0
632
- * @param int $bother_length Min Character length to bother you with.
633
- */
634
- $bother_length = (int) \apply_filters( 'the_seo_framework_bother_me_desc_length', $bother_length );
635
-
636
  $words = [];
637
  foreach ( $word_list as $wli ) {
638
  //= { $words[ int Offset ] => string Word }
@@ -641,31 +636,27 @@ class Core {
641
 
642
  $word_count = array_count_values( $words );
643
 
644
- //* Parse word counting.
645
- if ( is_array( $word_count ) ) {
646
- //* We're going to fetch words based on position, and then flip it to become the key.
647
- $word_keys = array_flip( array_reverse( $words, true ) );
648
 
649
- foreach ( $word_count as $word => $count ) {
650
- if ( mb_strlen( $word ) < $bother_length ) {
651
- $run = $count >= $amount_bother;
652
- } else {
653
- $run = $count >= $amount;
654
- }
655
 
656
- if ( $run ) {
657
- //* The encoded word is longer or equal to the bother length.
 
 
 
 
658
 
659
- //! Don't use mb_* here. preg_split's offset is in bytes, NOT unicode.
660
- $args = [
661
- 'pos' => $word_keys[ $word ],
662
- 'len' => strlen( $word ),
663
- ];
664
- $first_encountered_word = substr( $string, $args['pos'], $args['len'] );
665
 
666
- //* Found words that are used too frequently.
667
- $words_too_many[] = [ $first_encountered_word => $count ];
668
- }
669
  }
670
  }
671
  endif;
@@ -683,7 +674,7 @@ class Core {
683
  * @link https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-contrast
684
  * @link https://www.w3.org/WAI/GL/wiki/Relative_luminance
685
  *
686
- * @param string $hex The 3 to 6 character RGB hex. '#' prefix is supported.
687
  * @return string The hexadecimal RGB relative font color, without '#' prefix.
688
  */
689
  public function get_relative_fontcolor( $hex = '' ) {
@@ -706,7 +697,7 @@ class Core {
706
  $v /= 255;
707
 
708
  if ( $v > .03928 ) {
709
- $lum = pow( ( $v + .055 ) / 1.055, 2.4 );
710
  } else {
711
  $lum = $v / 12.92;
712
  }
@@ -741,6 +732,38 @@ class Core {
741
  return $retr . $retg . $retb;
742
  }
743
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
744
  /**
745
  * Converts markdown text into HMTL.
746
  * Does not support list or block elements. Only inline statements.
@@ -754,10 +777,10 @@ class Core {
754
  * @since 2.9.3 : Added $args parameter.
755
  * @link https://wordpress.org/plugins/about/readme.txt
756
  *
757
- * @param string $text The text that might contain markdown. Expected to be escaped.
758
- * @param array $convert The markdown style types wished to be converted.
759
- * If left empty, it will convert all.
760
- * @param array $args The function arguments.
761
  * @return string The markdown converted text.
762
  */
763
  public function convert_markdown( $text, $convert = [], $args = [] ) {
@@ -768,13 +791,12 @@ class Core {
768
  $text = trim( $text );
769
  }
770
 
771
- if ( '' === $text )
 
772
  return '';
773
 
774
  // Merge defaults with $args.
775
- $args = array_merge( [
776
- 'a_internal' => false,
777
- ], $args );
778
 
779
  /**
780
  * The conversion list's keys are per reference only.
@@ -861,7 +883,7 @@ class Core {
861
  for ( $i = 0; $i < $count; $i++ ) {
862
  $text = str_replace(
863
  $matches[0][ $i ],
864
- sprintf( $_string, \esc_url( $matches[2][ $i ], [ 'http', 'https' ] ), \esc_html( $matches[1][ $i ] ) ),
865
  $text
866
  );
867
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Core
4
+ * @see ./index.php for facade details.
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
33
  * @since 2.8.0
34
  */
35
  class Core {
36
+ use Traits\Enclose_Core_Final;
37
 
38
  /**
39
  * Tells if this plugin is loaded.
43
  * @since 3.1.0
44
  * @access protected
45
  * Don't alter this variable!!!
46
+ * @var boolean $loaded
47
  */
48
  public $loaded = false;
49
 
52
  */
53
  private function __construct() { }
54
 
 
 
 
 
 
 
 
 
 
 
55
  /**
56
  * Handles unapproachable invoked properties.
57
  *
61
  * @since 2.8.0
62
  * @since 3.2.2 This method no longer allows to overwrite protected or private variables.
63
  *
64
+ * @param string $name The property name.
65
+ * @param mixed $value The property value.
66
  */
67
  final public function __set( $name, $value ) {
68
  /**
89
  */
90
  final public function __get( $name ) {
91
  $this->_inaccessible_p_or_m( 'the_seo_framework()->' . $name, 'unknown' );
 
92
  }
93
 
94
  /**
96
  *
97
  * @since 2.7.0
98
  *
99
+ * @param string $name The method name.
100
+ * @param array $arguments The method arguments.
101
+ * @return mixed|void
102
  */
103
  final public function __call( $name, $arguments ) {
104
 
112
  }
113
 
114
  \the_seo_framework()->_inaccessible_p_or_m( 'the_seo_framework()->' . $name . '()' );
 
115
  }
116
 
117
  /**
118
  * Destroys output buffer, if any. To be used with AJAX and XML to clear any PHP errors or dumps.
119
  *
120
  * @since 2.8.0
121
+ * @since 2.9.0 Now flushes all levels rather than just the latest one.
122
+ * @since 4.0.0 Is now public.
123
  *
124
  * @return bool True on clear. False otherwise.
125
  */
126
+ public function clean_response_header() {
127
 
128
+ $level = ob_get_level();
129
+
130
+ if ( $level ) {
131
+ while ( $level-- ) ob_end_clean();
132
  return true;
133
  }
134
 
143
  * @access private
144
  * @credits Akismet For some code.
145
  *
146
+ * @param string $view The file name.
147
+ * @param array $__args The arguments to be supplied within the file name.
148
+ * Each array key is converted to a variable with its value attached.
149
  * @param string $instance The instance suffix to call back upon.
150
  */
151
  public function get_view( $view, array $__args = [], $instance = 'main' ) {
170
  }
171
 
172
  /**
173
+ * Fetches view instance for view-switch statements.
174
  *
175
  * @since 2.7.0
176
  *
188
  *
189
  * @since 2.6.0
190
  *
191
+ * @param int $i The dimension to resize.
192
  * @param int $r1 The deminsion that determines the ratio.
193
  * @param int $r2 The dimension to proportionate to.
194
  * @return int The proportional dimension, rounded.
197
 
198
  //* Get aspect ratio.
199
  $ar = $r1 / $r2;
200
+ $i = $i / $ar;
201
 
 
202
  return round( $i );
203
  }
204
 
222
 
223
  $tsf_links['about'] = sprintf(
224
  '<a href="https://theseoframework.com/about-us/" rel="noreferrer noopener nofollow" target="_blank">%s</a>',
225
+ \esc_html_x( 'About', 'About us', 'autodescription' )
226
  );
227
  $tsf_links['tsfem'] = sprintf(
228
  '<a href="%s" rel="noreferrer noopener" target="_blank">%s</a>',
250
  if ( THE_SEO_FRAMEWORK_PLUGIN_BASENAME !== $plugin_file )
251
  return $plugin_meta;
252
 
253
+ $plugins = \get_plugins();
254
+ $_get_em = empty( $plugins['the-seo-framework-extension-manager/the-seo-framework-extension-manager.php'] );
255
+
256
+ return array_merge(
257
+ $plugin_meta,
258
+ [
259
+ 'docs' => vsprintf(
260
+ '<a href="%s" rel="noreferrer noopener nofollow" target="_blank">%s</a>',
261
+ [
262
+ 'https://tsf.fyi/docs',
263
+ \esc_html__( 'View documentation', 'autodescription' ),
264
+ ]
265
+ ),
266
+ 'API' => vsprintf(
267
+ '<a href="%s" rel="noreferrer noopener nofollow" target="_blank">%s</a>',
268
+ [
269
+ 'https://tsf.fyi/docs/api',
270
+ \esc_html__( 'View API docs', 'autodescription' ),
271
+ ]
272
+ ),
273
+ 'EM' => vsprintf(
274
+ '<a href="%s" rel="noreferrer noopener nofollow" target="_blank">%s</a>',
275
+ [
276
+ 'https://tsf.fyi/extension-manager',
277
+ $_get_em ? \esc_html_x( 'Get the Extension Manager', 'Extension Manager is a product name; do not translate it.', 'autodescription' ) : 'Extension Manager',
278
+ ]
279
+ ),
280
+ ]
281
+ );
282
+ }
283
+
284
+ /**
285
+ * Returns an array of hierarchical post types.
286
+ *
287
+ * @since 4.0.0
288
+ *
289
+ * @return array The public hierarchical post types with rewrite.
290
+ */
291
+ public function get_hierarchical_post_types() {
292
+ static $types;
293
+ return $types ?: $types = \get_post_types(
294
+ [
295
+ 'hierarchical' => true,
296
+ 'public' => true,
297
+ 'rewrite' => true,
298
+ ],
299
+ 'names'
300
+ );
301
+ }
302
+
303
+ /**
304
+ * Returns an array of nonhierarchical post types.
305
+ *
306
+ * @since 4.0.0
307
+ *
308
+ * @return array The public nonhierarchical post types with rewrite.
309
+ */
310
+ public function get_nonhierarchical_post_types() {
311
+ static $types;
312
+ return $types ?: $types = \get_post_types(
313
+ [
314
+ 'hierarchical' => false,
315
+ 'public' => true,
316
+ 'rewrite' => true,
317
+ ],
318
+ 'names'
319
+ );
320
  }
321
 
322
  /**
385
  return false;
386
  }
387
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  /**
389
  * Returns the minimum role required to adjust settings.
390
  *
426
 
427
  $url = html_entity_decode( \menu_page_url( $this->seo_settings_page_slug, false ) );
428
 
429
+ return \esc_url( $url, [ 'https', 'http' ] );
430
  }
431
 
432
  return '';
460
  * Fetches the Timezone String from given offset.
461
  *
462
  * @since 2.6.0
463
+ * @since 4.0.0 Removed PHP <5.6 support.
464
  *
465
  * @param int $offset The GMT offzet.
466
  * @return string PHP Timezone String.
467
  */
468
  protected function get_tzstring_from_offset( $offset = 0 ) {
469
 
470
+ $seconds = round( $offset * HOUR_IN_SECONDS );
 
 
471
  $tzstring = timezone_name_from_abbr( '', $seconds, 1 );
 
 
 
 
 
 
472
 
473
  return $tzstring;
474
  }
483
  * @since 3.0.6 Now uses the old timezone string when a new one can't be generated.
484
  *
485
  * @param string $tzstring Optional. The PHP Timezone string. Best to leave empty to always get a correct one.
486
+ * @link http://php.net/manual/en/timezones.php
487
+ * @param bool $reset Whether to reset to default. Ignoring first parameter.
488
  * @return bool True on success. False on failure.
489
  */
490
  public function set_timezone( $tzstring = '', $reset = false ) {
549
  * @return string The timestamp format used in PHP date.
550
  */
551
  public function get_timestamp_format() {
552
+ return $this->uses_time_in_timestamp_format() ? 'Y-m-d\TH:iP' : 'Y-m-d';
553
  }
554
 
555
  /**
591
  * @since 2.7.0
592
  * @since 3.1.0 This method now uses PHP 5.4+ encoding, capable of UTF-8 interpreting,
593
  * instead of relying on PHP's incomplete encoding table.
594
+ * This does mean that the functionality is crippled when the PHP
595
  * installation isn't unicode compatible; this is unlikely.
596
+ * @since 4.0.0 1. Now expects PCRE UTF-8 encoding support.
597
+ * 2. Moved filter outside of this function.
598
+ * 3. Short length now works as intended, instead of comparing as less, it compares as less or equal to.
599
+ * @staticvar bool $use_mb Determines whether we can use mb_* functions.
600
  *
601
  * @param string $string Required. The string to count words in.
602
+ * @param int $dupe_count Minimum amount of words to encounter in the string.
603
+ * Set to 0 to count all words longer than $short_length.
604
+ * @param int $dupe_short Minimum amount of words to encounter in the string that fall under the
605
+ * $short_length. Set to 0 to consider all words with $amount.
606
+ * @param int $short_length The maximum string length of a word to pass for $dupe_short
607
+ * instead of $count. Set to 0 to ignore $count, and use $dupe_short only.
 
 
608
  * @return array Containing arrays of words with their count.
609
  */
610
+ public function get_word_count( $string, $dupe_count = 3, $dupe_short = 5, $short_length = 3 ) {
611
 
612
  $string = html_entity_decode( $string );
613
+ $string = \wp_check_invalid_utf8( $string );
614
 
615
+ if ( ! $string ) return [];
616
+
617
+ static $use_mb;
618
+
619
+ isset( $use_mb ) or $use_mb = extension_loaded( 'mbstring' );
620
+
621
+ $word_list = preg_split(
622
+ '/[^\p{L}\p{M}\p{N}\p{Pc}\p{Cc}]+/mu',
623
+ $use_mb ? mb_strtolower( $string ) : strtolower( $string ),
624
+ -1,
625
+ PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_NO_EMPTY
626
+ );
 
 
 
 
 
 
 
 
627
 
628
  $words_too_many = [];
629
 
630
  if ( count( $word_list ) ) :
 
 
 
 
 
 
631
  $words = [];
632
  foreach ( $word_list as $wli ) {
633
  //= { $words[ int Offset ] => string Word }
636
 
637
  $word_count = array_count_values( $words );
638
 
639
+ // We're going to fetch words based on position, and then flip it to become the key.
640
+ $word_keys = array_flip( array_reverse( $words, true ) );
 
 
641
 
642
+ foreach ( $word_count as $word => $count ) {
643
+ if ( ( $use_mb ? mb_strlen( $word ) : strlen( $word ) ) <= $short_length ) {
644
+ $run = $count >= $dupe_short;
645
+ } else {
646
+ $run = $count >= $dupe_count;
647
+ }
648
 
649
+ if ( $run ) {
650
+ //! Don't use mb_* here. preg_split's offset is in bytes, NOT multibytes.
651
+ $args = [
652
+ 'pos' => $word_keys[ $word ],
653
+ 'len' => strlen( $word ),
654
+ ];
655
 
656
+ $first_encountered_word = substr( $string, $args['pos'], $args['len'] );
 
 
 
 
 
657
 
658
+ //* Found words that are used too frequently.
659
+ $words_too_many[] = [ $first_encountered_word => $count ];
 
660
  }
661
  }
662
  endif;
674
  * @link https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast-contrast
675
  * @link https://www.w3.org/WAI/GL/wiki/Relative_luminance
676
  *
677
+ * @param string $hex The 3 to 6 character RGB hex. The '#' prefix may be added.
678
  * @return string The hexadecimal RGB relative font color, without '#' prefix.
679
  */
680
  public function get_relative_fontcolor( $hex = '' ) {
697
  $v /= 255;
698
 
699
  if ( $v > .03928 ) {
700
+ $lum = ( ( $v + .055 ) / 1.055 ) ** 2.4;
701
  } else {
702
  $lum = $v / 12.92;
703
  }
732
  return $retr . $retg . $retb;
733
  }
734
 
735
+ /**
736
+ * Returns sitemap color scheme.
737
+ *
738
+ * @since 2.8.0
739
+ *
740
+ * @param bool $get_defaults Whether to get the default colors.
741
+ * @return array The sitemap colors.
742
+ */
743
+ public function get_sitemap_colors( $get_defaults = false ) {
744
+
745
+ if ( $get_defaults ) {
746
+ $colors = [
747
+ 'main' => '#333',
748
+ 'accent' => '#00cd98',
749
+ ];
750
+ } else {
751
+ $main = $this->s_color_hex( $this->get_option( 'sitemap_color_main' ) );
752
+ $accent = $this->s_color_hex( $this->get_option( 'sitemap_color_accent' ) );
753
+
754
+ $options = [
755
+ 'main' => $main ? '#' . $main : '',
756
+ 'accent' => $accent ? '#' . $accent : '',
757
+ ];
758
+
759
+ $options = array_filter( $options );
760
+
761
+ $colors = array_merge( $this->get_sitemap_colors( true ), $options );
762
+ }
763
+
764
+ return $colors;
765
+ }
766
+
767
  /**
768
  * Converts markdown text into HMTL.
769
  * Does not support list or block elements. Only inline statements.
777
  * @since 2.9.3 : Added $args parameter.
778
  * @link https://wordpress.org/plugins/about/readme.txt
779
  *
780
+ * @param string $text The text that might contain markdown. Expected to be escaped.
781
+ * @param array $convert The markdown style types wished to be converted.
782
+ * If left empty, it will convert all.
783
+ * @param array $args The function arguments.
784
  * @return string The markdown converted text.
785
  */
786
  public function convert_markdown( $text, $convert = [], $args = [] ) {
791
  $text = trim( $text );
792
  }
793
 
794
+ // You need 3 chars to make a markdown: *m*
795
+ if ( strlen( $text ) < 3 )
796
  return '';
797
 
798
  // Merge defaults with $args.
799
+ $args = array_merge( [ 'a_internal' => false ], $args );
 
 
800
 
801
  /**
802
  * The conversion list's keys are per reference only.
883
  for ( $i = 0; $i < $count; $i++ ) {
884
  $text = str_replace(
885
  $matches[0][ $i ],
886
+ sprintf( $_string, \esc_url( $matches[2][ $i ], [ 'https', 'http' ] ), \esc_html( $matches[1][ $i ] ) ),
887
  $text
888
  );
889
  }
inc/classes/debug.class.php CHANGED
@@ -1,11 +1,15 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
8
 
 
 
9
  /**
10
  * The SEO Framework plugin
11
  * Copyright (C) 2015 - 2019 Sybre Waaijer, CyberWire (https://cyberwire.nl/)
@@ -29,48 +33,35 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
29
  * Holds plugin debug functions.
30
  *
31
  * @since 2.8.0
 
32
  */
33
- final class Debug implements Debug_Interface {
 
34
 
35
  /**
36
- * The object instance.
37
- *
38
  * @since 2.8.0
39
- *
40
- * @var object|null This object instance.
41
  */
42
  private static $instance = null;
43
 
44
  /**
45
- * Cached debug/profile properties.
46
- *
47
  * @since 2.8.0
48
- *
49
- * @var bool Whether debug is enabled.
50
- * @var bool Whether debug is hidden in HTMl.
51
  */
52
  public $the_seo_framework_debug = false;
53
 
54
- /**
55
- * Unserializing instances of this object is forbidden.
56
- */
57
- final protected function __wakeup() { }
58
-
59
- /**
60
- * Cloning of this object is forbidden.
61
- */
62
- final protected function __clone() { }
63
-
64
  /**
65
  * Constructor.
66
  */
67
- final protected function __construct() {}
68
 
69
  /**
70
  * Sets the class instance.
71
  *
72
  * @since 3.1.0
73
  * @access private
 
 
74
  */
75
  public static function _set_instance( $debug = null ) {
76
 
@@ -110,7 +101,7 @@ final class Debug implements Debug_Interface {
110
  * @param string $replacement Optional. The function that should have been called. Default null.
111
  */
112
  public function _deprecated_filter( $filter, $version, $replacement = null ) {
113
- $this->_deprecated_function( 'Filter ' . $filter, $version, $replacement ); // ignore invalid xss warnings.
114
  }
115
 
116
  /**
@@ -129,7 +120,7 @@ final class Debug implements Debug_Interface {
129
  * @param string $replacement Optional. The function that should have been called. Default null.
130
  * Expected to be escaped.
131
  */
132
- public function _deprecated_function( $function, $version, $replacement = null ) { // phpcs:ignore -- xss ok.
133
  /**
134
  * Fires when a deprecated function is called.
135
  *
@@ -160,9 +151,9 @@ final class Debug implements Debug_Interface {
160
  \esc_html( $function ),
161
  '<strong>' . \esc_html__( 'deprecated', 'autodescription' ) . '</strong>',
162
  \esc_html( $version ),
163
- $replacement
164
  )
165
- ); // xss ok: $replacement is expected to be escaped.
166
  } else {
167
  trigger_error(
168
  sprintf(
@@ -191,10 +182,10 @@ final class Debug implements Debug_Interface {
191
  * @access private
192
  *
193
  * @param string $function The function that was called.
194
- * @param string $message A message explaining what has been done incorrectly.
195
  * @param string $version The version of WordPress where the message was added.
196
  */
197
- public function _doing_it_wrong( $function, $message, $version = null ) { // phpcs:ignore -- xss ok.
198
  /**
199
  * Fires when the given function is being used incorrectly.
200
  *
@@ -225,10 +216,10 @@ final class Debug implements Debug_Interface {
225
  \esc_html__( '%1$s was called %2$s. %3$s %4$s', 'autodescription' ),
226
  \esc_html( $function ),
227
  '<strong>' . \esc_html__( 'incorrectly', 'autodescription' ) . '</strong>',
228
- $message,
229
  \esc_html( $version )
230
  )
231
- ); // xss ok: $message is expected to be escaped.
232
 
233
  restore_error_handler();
234
  }
@@ -298,7 +289,8 @@ final class Debug implements Debug_Interface {
298
  */
299
  protected function get_error() {
300
 
301
- $backtrace = debug_backtrace();
 
302
  /**
303
  * 0 = This function.
304
  * 1 = Error handler.
@@ -327,19 +319,18 @@ final class Debug implements Debug_Interface {
327
  /**
328
  * The SEO Framework error handler.
329
  *
330
- * Only handles user notices.
331
- * @see E_USER_NOTICE
332
  *
333
  * @since 2.6.0
334
  * @since 3.2.2 Fixed unaccounted-for backtrace depth logic since this class decoupling in 3.1
335
  *
336
- * @param int Error handling code.
337
- * @param string The error message.
338
  */
339
  protected function error_handler_deprecated( $code, $message ) {
340
 
341
- //* Only do so if E_USER_NOTICE is pased.
342
- if ( E_USER_NOTICE === $code && isset( $message ) ) {
343
  $this->error_handler( $this->get_error(), $message );
344
  }
345
  }
@@ -351,8 +342,8 @@ final class Debug implements Debug_Interface {
351
  *
352
  * @since 2.6.0
353
  *
354
- * @param int Error handling code.
355
- * @param string The error message.
356
  */
357
  protected function error_handler_doing_it_wrong( $code, $message ) {
358
 
@@ -370,8 +361,8 @@ final class Debug implements Debug_Interface {
370
  * @since 2.6.0
371
  * @since 3.2.2 Fixed unaccounted-for backtrace depth logic since this class decoupling in 3.1
372
  *
373
- * @param int Error handling code.
374
- * @param string The error message.
375
  */
376
  protected function error_handler_inaccessible_call( $code, $message ) {
377
 
@@ -387,9 +378,9 @@ final class Debug implements Debug_Interface {
387
  * @since 2.6.0
388
  * @since 2.8.0 added $code parameter
389
  *
390
- * @param array $error The Error location and file data extruded from debug_backtrace().
391
  * @param string $message The error message. Expected to be escaped.
392
- * @param int $code The error handler code.
393
  */
394
  protected function error_handler( $error, $message, $code = E_USER_NOTICE ) {
395
 
@@ -412,7 +403,8 @@ final class Debug implements Debug_Interface {
412
  break;
413
  endswitch;
414
 
415
- echo sprintf( '<span><strong>%s:</strong> ', $type ) . $message; // xss ok
 
416
  echo $file ? ' In ' . \esc_html( $file ) : '';
417
  echo $line ? ' on line ' . \esc_html( $line ) : '';
418
  echo '.</span><br>' . PHP_EOL;
@@ -452,7 +444,6 @@ final class Debug implements Debug_Interface {
452
  * 2. Now is protected.
453
  *
454
  * @param string $value The debug value.
455
- * @param bool $ignore Ignore the hidden output.
456
  * @return string
457
  */
458
  protected function debug_value_wrapper( $value ) {
@@ -469,8 +460,8 @@ final class Debug implements Debug_Interface {
469
  * @since 2.6.0
470
  * @since 3.1.0 Now is protected.
471
  *
472
- * @param bool $set Whether to reset the timer.
473
- * @return float PHP Microtime for code execution.
474
  */
475
  protected function timer( $reset = false ) {
476
 
@@ -493,7 +484,8 @@ final class Debug implements Debug_Interface {
493
  * @access private
494
  */
495
  public static function _output_debug_header() {
496
- echo static::get_instance()->get_debug_header_output(); // xss ok.
 
497
  }
498
 
499
  /**
@@ -508,7 +500,7 @@ final class Debug implements Debug_Interface {
508
 
509
  $tsf = \the_seo_framework();
510
 
511
- if ( $tsf->is_admin() && ! $tsf->is_term_edit() && ! $tsf->is_post_edit() && ! $tsf->is_seo_settings_page( true ) )
512
  return;
513
 
514
  if ( $tsf->is_seo_settings_page( true ) )
@@ -548,12 +540,12 @@ final class Debug implements Debug_Interface {
548
 
549
  $timer = '<div style="display:inline-block;width:100%;padding:20px;border-bottom:1px solid #ccc;">Generated in: ' . number_format( $this->timer(), 5 ) . ' seconds</div>';
550
 
551
- $title = $tsf->is_admin() ? 'Expected SEO Output' : 'Determined SEO Output';
552
  $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>';
553
 
554
  //* Escape it, replace EOL with breaks, and style everything between quotes (which are ending with space).
555
- $output = str_replace( PHP_EOL, '<br>' . PHP_EOL, esc_html( $output ) );
556
- $output = preg_replace( '/(&quot;.*?&quot;)(\s)/', '<font color="arnoldschwarzenegger">$1</font> ', $output );
557
 
558
  $output = '<div style="display:inline-block;width:100%;padding:20px;font-family:Consolas,Monaco,monospace;font-size:14px;">' . $output . '</div>';
559
  $output = '<div style="display:block;width:100%;background:#23282D;color:#ddd;border-bottom:1px solid #ccc">' . $title . $timer . $output . '</div>';
@@ -568,7 +560,8 @@ final class Debug implements Debug_Interface {
568
  * @access private
569
  */
570
  public static function _output_debug_query() {
571
- echo static::$instance->get_debug_query_output(); // xss ok
 
572
  }
573
 
574
  /**
@@ -578,7 +571,8 @@ final class Debug implements Debug_Interface {
578
  * @access private
579
  */
580
  public static function _output_debug_query_from_cache() {
581
- echo static::$instance->get_debug_query_output_from_cache(); // xss ok
 
582
  }
583
 
584
  /**
@@ -614,8 +608,7 @@ final class Debug implements Debug_Interface {
614
  * Wraps query status booleans in human-readable code.
615
  *
616
  * @since 2.6.6
617
- * @global bool $multipage
618
- * @global int $numpages
619
  *
620
  * @param string $cache_version 'yup' or 'nope'
621
  * @return string Wrapped Query State debug output.
@@ -625,54 +618,56 @@ final class Debug implements Debug_Interface {
625
  //* Start timer.
626
  $this->timer( true );
627
 
628
- global $multipage, $numpages;
629
-
630
  $tsf = \the_seo_framework();
631
 
632
  //* Only get true/false values.
633
- $page_id = $tsf->get_the_real_ID();
634
- $is_404 = $tsf->is_404();
635
- $is_admin = $tsf->is_admin();
636
- $is_attachment = $tsf->is_attachment();
637
- $is_archive = $tsf->is_archive();
638
- $is_term_edit = $tsf->is_term_edit();
639
- $is_post_edit = $tsf->is_post_edit();
640
- $is_wp_lists_edit = $tsf->is_wp_lists_edit();
641
- $is_author = $tsf->is_author();
642
- $is_blog_page = $tsf->is_blog_page();
643
- $is_category = $tsf->is_category();
644
- $is_date = $tsf->is_date();
645
- $is_year = $tsf->is_year();
646
- $is_month = $tsf->is_month();
647
- $is_day = $tsf->is_day();
648
- $is_feed = $tsf->is_feed();
649
- $is_real_front_page = $tsf->is_real_front_page();
650
- $is_front_page_by_id = $tsf->is_front_page_by_id( $tsf->get_the_real_ID() );
651
- $is_home = $tsf->is_home();
652
- $is_page = $tsf->is_page();
653
- $page = $tsf->page();
654
- $paged = $tsf->paged();
655
- $is_preview = $tsf->is_preview();
656
- $is_search = $tsf->is_search();
657
- $is_single = $tsf->is_single();
658
- $is_singular = $tsf->is_singular();
659
- $is_static_frontpage = $tsf->is_static_frontpage();
660
- $is_tag = $tsf->is_tag();
661
- $is_tax = $tsf->is_tax();
662
- $is_wc_shop = $tsf->is_wc_shop();
663
- $is_wc_product = $tsf->is_wc_product();
 
664
  $is_seo_settings_page = $tsf->is_seo_settings_page( true );
665
- $numpages = $tsf->numpages();
666
- $is_multipage = $tsf->is_multipage();
667
- $is_singular_archive = $tsf->is_singular_archive();
 
 
668
 
669
  //* Don't debug the class object.
670
  unset( $tsf );
671
 
672
  //* Get all above vars, split them in two (true and false) and sort them by key names.
673
- $vars = get_defined_vars();
674
- $current = array_filter( $vars );
675
  $not_current = array_diff_key( $vars, $current );
 
676
  ksort( $current );
677
  ksort( $not_current );
678
 
@@ -688,8 +683,8 @@ final class Debug implements Debug_Interface {
688
  $value = \esc_attr( var_export( $value, true ) );
689
  }
690
 
691
- $value = '<font color="harrisonford">' . $type . ' ' . $value . '</font>';
692
- $out = \esc_html( $name ) . ' => ' . $value;
693
  $output .= '<span style="background:#dadada">' . $out . '</span>' . PHP_EOL;
694
  }
695
 
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
15
  * Copyright (C) 2015 - 2019 Sybre Waaijer, CyberWire (https://cyberwire.nl/)
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
+ use Traits\Enclose_Stray_Private;
40
 
41
  /**
 
 
42
  * @since 2.8.0
43
+ * @var object|null $instance This object instance.
 
44
  */
45
  private static $instance = null;
46
 
47
  /**
 
 
48
  * @since 2.8.0
49
+ * @var bool $the_seo_framework_debug Whether debug is enabled.
 
 
50
  */
51
  public $the_seo_framework_debug = false;
52
 
 
 
 
 
 
 
 
 
 
 
53
  /**
54
  * Constructor.
55
  */
56
+ protected function __construct() {}
57
 
58
  /**
59
  * Sets the class instance.
60
  *
61
  * @since 3.1.0
62
  * @access private
63
+ *
64
+ * @param bool|null $debug Whether TSF debugging is enabled.
65
  */
66
  public static function _set_instance( $debug = null ) {
67
 
101
  * @param string $replacement Optional. The function that should have been called. Default null.
102
  */
103
  public function _deprecated_filter( $filter, $version, $replacement = null ) {
104
+ $this->_deprecated_function( 'Filter ' . $filter, $version, $replacement ); // phpcs:ignore -- Wrong asserts, copied method name.
105
  }
106
 
107
  /**
120
  * @param string $replacement Optional. The function that should have been called. Default null.
121
  * Expected to be escaped.
122
  */
123
+ public function _deprecated_function( $function, $version, $replacement = null ) { // phpcs:ignore -- Wrong asserts, copied method name.
124
  /**
125
  * Fires when a deprecated function is called.
126
  *
151
  \esc_html( $function ),
152
  '<strong>' . \esc_html__( 'deprecated', 'autodescription' ) . '</strong>',
153
  \esc_html( $version ),
154
+ $replacement // phpcs:ignore, WordPress.Security.EscapeOutput -- See doc comment.
155
  )
156
+ );
157
  } else {
158
  trigger_error(
159
  sprintf(
182
  * @access private
183
  *
184
  * @param string $function The function that was called.
185
+ * @param string $message A message explaining what has been done incorrectly. Must be escaped.
186
  * @param string $version The version of WordPress where the message was added.
187
  */
188
+ public function _doing_it_wrong( $function, $message, $version = null ) { // phpcs:ignore -- Wrong asserts, copied method name.
189
  /**
190
  * Fires when the given function is being used incorrectly.
191
  *
216
  \esc_html__( '%1$s was called %2$s. %3$s %4$s', 'autodescription' ),
217
  \esc_html( $function ),
218
  '<strong>' . \esc_html__( 'incorrectly', 'autodescription' ) . '</strong>',
219
+ $message, // phpcs:ignore, WordPress.Security.EscapeOutput -- See doc comment.
220
  \esc_html( $version )
221
  )
222
+ );
223
 
224
  restore_error_handler();
225
  }
289
  */
290
  protected function get_error() {
291
 
292
+ // phpcs:ignore, WordPress.PHP.NoSilencedErrors -- Feature may be disabled.
293
+ $backtrace = @debug_backtrace();
294
  /**
295
  * 0 = This function.
296
  * 1 = Error handler.
319
  /**
320
  * The SEO Framework error handler.
321
  *
322
+ * Only handles user notices: E_USER_NOTICE
 
323
  *
324
  * @since 2.6.0
325
  * @since 3.2.2 Fixed unaccounted-for backtrace depth logic since this class decoupling in 3.1
326
  *
327
+ * @param int $code The error handler code.
328
+ * @param string $message The error message. Expected to be escaped.
329
  */
330
  protected function error_handler_deprecated( $code, $message ) {
331
 
332
+ //* Only do so if E_USER_NOTICE is passed.
333
+ if ( E_USER_NOTICE & $code && isset( $message ) ) {
334
  $this->error_handler( $this->get_error(), $message );
335
  }
336
  }
342
  *
343
  * @since 2.6.0
344
  *
345
+ * @param int $code The error handler code.
346
+ * @param string $message The error message. Expected to be escaped.
347
  */
348
  protected function error_handler_doing_it_wrong( $code, $message ) {
349
 
361
  * @since 2.6.0
362
  * @since 3.2.2 Fixed unaccounted-for backtrace depth logic since this class decoupling in 3.1
363
  *
364
+ * @param int $code The error handler code.
365
+ * @param string $message The error message. Expected to be escaped.
366
  */
367
  protected function error_handler_inaccessible_call( $code, $message ) {
368
 
378
  * @since 2.6.0
379
  * @since 2.8.0 added $code parameter
380
  *
381
+ * @param array $error The Error location and file data extruded from debug_backtrace().
382
  * @param string $message The error message. Expected to be escaped.
383
+ * @param int $code The error handler code.
384
  */
385
  protected function error_handler( $error, $message, $code = E_USER_NOTICE ) {
386
 
403
  break;
404
  endswitch;
405
 
406
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- output is escaped.
407
+ echo sprintf( '<span><strong>%s:</strong> ', $type ) . $message;
408
  echo $file ? ' In ' . \esc_html( $file ) : '';
409
  echo $line ? ' on line ' . \esc_html( $line ) : '';
410
  echo '.</span><br>' . PHP_EOL;
444
  * 2. Now is protected.
445
  *
446
  * @param string $value The debug value.
 
447
  * @return string
448
  */
449
  protected function debug_value_wrapper( $value ) {
460
  * @since 2.6.0
461
  * @since 3.1.0 Now is protected.
462
  *
463
+ * @param bool $reset Whether to reset the timer.
464
+ * @return float The time it took for code execution.
465
  */
466
  protected function timer( $reset = false ) {
467
 
484
  * @access private
485
  */
486
  public static function _output_debug_header() {
487
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- callee escapes.
488
+ echo static::get_instance()->get_debug_header_output();
489
  }
490
 
491
  /**
500
 
501
  $tsf = \the_seo_framework();
502
 
503
+ if ( \is_admin() && ! $tsf->is_term_edit() && ! $tsf->is_post_edit() && ! $tsf->is_seo_settings_page( true ) )
504
  return;
505
 
506
  if ( $tsf->is_seo_settings_page( true ) )
540
 
541
  $timer = '<div style="display:inline-block;width:100%;padding:20px;border-bottom:1px solid #ccc;">Generated in: ' . number_format( $this->timer(), 5 ) . ' seconds</div>';
542
 
543
+ $title = \is_admin() ? 'Expected SEO Output' : 'Determined SEO Output';
544
  $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>';
545
 
546
  //* Escape it, replace EOL with breaks, and style everything between quotes (which are ending with space).
547
+ $output = str_replace( PHP_EOL, '<br>' . PHP_EOL, \esc_html( str_replace( str_repeat( ' ', 4 ), str_repeat( '&nbsp;', 4 ), $output ) ) );
548
+ $output = preg_replace( '/(&quot;.*?&quot;)(\s|&nbps;)/', '<font color="arnoldschwarzenegger">$1</font> ', $output );
549
 
550
  $output = '<div style="display:inline-block;width:100%;padding:20px;font-family:Consolas,Monaco,monospace;font-size:14px;">' . $output . '</div>';
551
  $output = '<div style="display:block;width:100%;background:#23282D;color:#ddd;border-bottom:1px solid #ccc">' . $title . $timer . $output . '</div>';
560
  * @access private
561
  */
562
  public static function _output_debug_query() {
563
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- This escapes.
564
+ echo static::$instance->get_debug_query_output();
565
  }
566
 
567
  /**
571
  * @access private
572
  */
573
  public static function _output_debug_query_from_cache() {
574
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- This escapes.
575
+ echo static::$instance->get_debug_query_output_from_cache();
576
  }
577
 
578
  /**
608
  * Wraps query status booleans in human-readable code.
609
  *
610
  * @since 2.6.6
611
+ * @since 4.0.0 Cleaned up global callers; only use TSF methods.
 
612
  *
613
  * @param string $cache_version 'yup' or 'nope'
614
  * @return string Wrapped Query State debug output.
618
  //* Start timer.
619
  $this->timer( true );
620
 
 
 
621
  $tsf = \the_seo_framework();
622
 
623
  //* Only get true/false values.
624
+ $page_id = $tsf->get_the_real_ID();
625
+ $is_404 = $tsf->is_404();
626
+ $is_admin = $tsf->is_admin();
627
+ $is_attachment = $tsf->is_attachment();
628
+ $is_archive = $tsf->is_archive();
629
+ $is_term_edit = $tsf->is_term_edit();
630
+ $is_post_edit = $tsf->is_post_edit();
631
+ $is_wp_lists_edit = $tsf->is_wp_lists_edit();
632
+ $is_author = $tsf->is_author();
633
+ $is_blog_page = $tsf->is_blog_page();
634
+ $is_category = $tsf->is_category();
635
+ $is_date = $tsf->is_date();
636
+ $is_year = $tsf->is_year();
637
+ $is_month = $tsf->is_month();
638
+ $is_day = $tsf->is_day();
639
+ $is_feed = $tsf->is_feed();
640
+ $is_real_front_page = $tsf->is_real_front_page();
641
+ $is_front_page_by_id = $tsf->is_front_page_by_id( $tsf->get_the_real_ID() );
642
+ $is_home = $tsf->is_home();
643
+ $is_page = $tsf->is_page();
644
+ $page = $tsf->page();
645
+ $paged = $tsf->paged();
646
+ $is_preview = $tsf->is_preview();
647
+ $is_customize_preview = $tsf->is_customize_preview();
648
+ $is_search = $tsf->is_search();
649
+ $is_single = $tsf->is_single();
650
+ $is_singular = $tsf->is_singular();
651
+ $is_static_frontpage = $tsf->is_static_frontpage();
652
+ $is_tag = $tsf->is_tag();
653
+ $is_tax = $tsf->is_tax();
654
+ $is_wc_shop = $tsf->is_wc_shop();
655
+ $is_wc_product = $tsf->is_wc_product();
656
  $is_seo_settings_page = $tsf->is_seo_settings_page( true );
657
+ $numpages = $tsf->numpages();
658
+ $is_multipage = $tsf->is_multipage();
659
+ $is_singular_archive = $tsf->is_singular_archive();
660
+ $is_term_meta_capable = $tsf->is_term_meta_capable();
661
+ $is_post_type_archive = \is_post_type_archive();
662
 
663
  //* Don't debug the class object.
664
  unset( $tsf );
665
 
666
  //* Get all above vars, split them in two (true and false) and sort them by key names.
667
+ $vars = get_defined_vars();
668
+ $current = array_filter( $vars );
669
  $not_current = array_diff_key( $vars, $current );
670
+
671
  ksort( $current );
672
  ksort( $not_current );
673
 
683
  $value = \esc_attr( var_export( $value, true ) );
684
  }
685
 
686
+ $value = '<font color="harrisonford">' . $type . ' ' . $value . '</font>';
687
+ $out = \esc_html( $name ) . ' => ' . $value;
688
  $output .= '<span style="background:#dadada">' . $out . '</span>' . PHP_EOL;
689
  }
690
 
inc/classes/deprecated.class.php CHANGED
@@ -1,8 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
4
- * @subpackage Classes\Deprecated
5
  */
 
6
  namespace The_SEO_Framework;
7
 
8
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -31,6 +32,7 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
31
  *
32
  * @since 2.8.0
33
  * @since 3.1.0: Removed all methods deprecated in 3.0.0.
 
34
  * @ignore
35
  */
36
  final class Deprecated {
@@ -41,628 +43,824 @@ final class Deprecated {
41
  public function __construct() { }
42
 
43
  /**
44
- * Returns the TSF meta output Object cache key.
45
  *
46
- * @since 2.8.0
47
- * @since 3.1.0 Deprecated.
48
  * @deprecated
49
- * @uses THE_SEO_FRAMEWORK_DB_VERSION as cache key buster.
50
- * @see $this->get_meta_output_cache_key_by_type();
51
  *
52
- * @param int $id The ID. Defaults to $this->get_the_real_ID();
53
- * @return string The TSF meta output cache key.
54
  */
55
- public function get_meta_output_cache_key( $id = 0 ) {
56
 
57
  $tsf = \the_seo_framework();
58
- $tsf->_deprecated_function( 'the_seo_framework()->get_meta_output_cache_key()', '3.1.0', 'the_seo_framework()->get_meta_output_cache_key_by_query()' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
- /**
61
- * Cache key buster.
62
- * Busts cache on each new db version.
63
- */
64
- $key = $tsf->generate_cache_key( $id ) . '_' . THE_SEO_FRAMEWORK_DB_VERSION;
65
 
66
- /**
67
- * Give each paged pages/archives a different cache key.
68
- * @since 2.2.6
69
- */
70
- $page = (string) $tsf->page();
71
- $paged = (string) $tsf->paged();
72
 
73
- return $cache_key = 'seo_framework_output_' . $key . '_' . $paged . '_' . $page;
 
 
74
  }
75
 
76
  /**
77
- * Alias of $this->get_preferred_scheme().
78
- * Typo.
79
  *
80
- * @since 2.8.0
81
- * @since 2.9.2 Added filter usage cache.
82
- * @since 3.0.0 Silently deprecated.
83
- * @since 3.1.0 Hard deprecated.
84
  * @deprecated
85
- * @staticvar string $scheme
86
  *
87
- * @return string The preferred URl scheme.
88
  */
89
- public function get_prefered_scheme() {
 
90
  $tsf = \the_seo_framework();
91
- $tsf->_deprecated_function( 'the_seo_framework()->get_prefered_scheme()', '3.1.0', 'the_seo_framework()->get_preferred_scheme()' );
92
- return $tsf->get_preferred_scheme();
 
 
 
 
 
 
 
 
 
 
 
93
  }
94
 
95
  /**
96
- * Cache description in static variable
97
- * Must be called inside the loop
98
  *
99
- * @since 2.2.2
 
100
  * @deprecated
101
- * @since 3.0.6 Silently deprecated.
102
- * @since 3.1.0 1. Hard deprecated.
103
- * 2. Removed caching, this is done at a deeper level.
104
  *
105
- * @param bool $social Determines whether the description is social.
106
- * @return string The description
107
  */
108
- public function description_from_cache( $social = false ) {
 
109
  $tsf = \the_seo_framework();
110
- $tsf->_deprecated_function( 'the_seo_framework()->description_from_cache()', '3.1.0', 'the_seo_framework()->get_description()' );
111
- return $tsf->generate_description( '', array( 'social' => $social ) );
 
 
 
 
 
112
  }
113
 
114
  /**
115
- * Gets the title. Main function.
116
- * Always use this function for the title unless you're absolutely sure what you're doing.
117
  *
118
- * This function is used for all these: Taxonomies and Terms, Posts, Pages, Blog, front page, front-end, back-end.
119
- *
120
- * @since 1.0.0
121
- * @since 3.1.0 Deprecated
122
  * @deprecated
 
 
 
 
 
 
 
 
123
  *
124
- * Params required wp_title filter :
125
- * @param string $title The Title to return
126
- * @param string $sep The Title sepeartor
127
- * @param string $seplocation The Title sepeartor location ( accepts 'left' or 'right' )
128
  *
129
- * @since 2.4.0:
130
- * @param array $args : accepted args : {
131
- * @param int term_id The Taxonomy Term ID when taxonomy is also filled in. Else post ID.
132
- * @param string taxonomy The Taxonomy name.
133
- * @param bool page_on_front Page on front condition for example generation.
134
- * @param bool placeholder Generate placeholder, ignoring options.
135
- * @param bool notagline Generate title without tagline.
136
- * @param bool meta Ignore doing_it_wrong. Used in og:title/twitter:title
137
- * @param bool get_custom_field Do not fetch custom title when false.
138
- * @param bool description_title Fetch title for description.
139
- * @param bool is_front_page Fetch front page title.
140
- * }
141
- * @param string $method The invoked method. @internal
142
- * @return string $title Title
143
  */
144
- public function title( $title = '', $sep = '', $seplocation = '', $args = [], $method = 'title' ) {
145
 
146
  $tsf = \the_seo_framework();
147
- $tsf->_deprecated_function(
148
- 'the_seo_framework()->' . \esc_html( $method ) . '()',
149
- '3.1.0',
150
- 'the_seo_framework()->get_title()'
151
- );
152
 
153
- if ( isset( $args['term_id'] ) ) {
154
- $new_args = [];
155
- $new_args['id'] = $args['term_id'];
156
- }
157
- if ( isset( $args['taxonomy'] ) ) {
158
- $new_args = isset( $new_args ) ? $new_args : [];
159
- $new_args['taxonomy'] = $args['taxonomy'];
160
  }
161
- if ( ! empty( $args['is_front_page'] ) || ! empty( $args['page_on_front'] ) ) {
162
- //= Overwrite args.
163
- $new_args = [ 'id' => $tsf->get_the_front_page_ID() ];
 
164
  }
165
 
166
- return $tsf->get_title( empty( $new_args ) ? null : $new_args );
167
  }
168
 
169
  /**
170
- * Builds the title based on input and query status.
171
  *
172
- * @since 2.4.0
173
- * @since 3.1.0 Deprecated
 
 
 
174
  * @deprecated
175
  *
176
- * @param string $title The Title to return
177
- * @param string $seplocation The Title sepeartor location ( accepts 'left' or 'right' )
178
- * @param array $args : accepted args : {
179
- * @param int term_id The Taxonomy Term ID
180
- * @param string taxonomy The Taxonomy name
181
- * @param bool page_on_front Page on front condition for example generation
182
- * @param bool placeholder Generate placeholder, ignoring options.
183
- * @param bool get_custom_field Do not fetch custom title when false.
184
- * @param bool is_front_page Fetch front page title.
185
- * }
186
- * @return string $title Title
187
  */
188
- public function build_title( $title = '', $seplocation = '', $args = [] ) {
189
- return $this->title( $title, '', $seplocation, $args, 'build_title' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  }
191
 
192
  /**
193
- * Generate the title based on conditions for the home page.
194
  *
195
- * @since 2.3.4
196
- * @since 2.3.8 Now checks tagline option.
197
- * @since 3.1.0 Deprecated.
 
198
  * @deprecated
199
  *
200
- * @param bool $get_custom_field Fetch Title from Custom Fields.
201
- * @param string $seplocation The separator location
202
- * @param string $deprecated Deprecated: The Home Page separator location
203
- * @param bool $escape Parse Title through saninitation calls.
204
- * @param bool $get_option Whether to fetch the SEO Settings option.
205
- * @return array {
206
- * 'title' => (string) $title : The Generated "Title"
207
- * 'blogname' => (string) $blogname : The Generated "Blogname"
208
- * 'add_tagline' => (bool) $add_tagline : Whether to add the tagline
209
- * 'seplocation' => (string) $seplocation : The Separator Location
210
- * }
211
  */
212
- public function generate_home_title() {
213
- $tsf = \the_seo_framework();
214
- $tsf->_deprecated_function( 'the_seo_framework()->generate_home_title()', '3.1.0', 'the_seo_framework()->get_title(...)' );
215
- return array(
216
- 'title' => $tsf->get_raw_generated_title( array( 'id' => $tsf->get_the_front_page_ID() ) ),
217
- 'blogname' => $tsf->get_home_page_tagline(),
218
- 'add_tagline' => $tsf->use_home_page_title_tagline(),
219
- 'seplocation' => $tsf->get_title_seplocation(),
220
- );
221
  }
222
 
223
  /**
224
- * Gets the archive Title, including filter. Also works in admin.
225
- *
226
- * @NOTE Taken from WordPress core. Altered to work for metadata.
227
- * @see WP Core get_the_archive_title()
228
  *
229
- * @since 2.6.0
230
- * @since 2.9.2 : Added WordPress core filter 'get_the_archive_title'
231
- * @since 3.0.4 : 1. Removed WordPress core filter 'get_the_archive_title'
232
- * 2. Added filter 'the_seo_framework_generated_archive_title'
233
- * @since 3.1.0 Deprecated.
234
  * @deprecated
235
  *
236
- * @param \WP_Term|null $term The Term object.
237
- * @param array $args The Title arguments.
238
- * @return string The Archive Title, not escaped.
239
  */
240
- public function get_the_real_archive_title( $term = null, $args = array() ) {
241
- $tsf = \the_seo_framework();
242
- $tsf->_deprecated_function( 'the_seo_framework()->get_the_real_archive_title()', '3.1.0', 'the_seo_framework()->get_generated_archive_title()' );
243
- return $tsf->get_generated_archive_title( $term );
244
  }
245
 
246
  /**
247
- * Determines whether to use a title prefix or not.
 
248
  *
249
  * @since 2.6.0
250
- * @since 3.0.0 Removed second parameter.
251
- * @since 3.1.0 Deprecated.
252
  * @deprecated
 
253
  *
254
- * @return bool
 
255
  */
256
- public function use_archive_prefix() {
257
- $tsf = \the_seo_framework();
258
- $tsf->_deprecated_function( 'the_seo_framework()->use_archive_prefix()', '3.1.0', 'the_seo_framework()->use_generated_archive_prefix()' );
259
- return $tsf->use_generated_archive_prefix();
 
 
 
 
 
 
260
  }
261
 
262
  /**
263
- * Returns untitled title.
 
264
  *
265
  * @since 2.6.0
266
- * @since 3.1.0 Deprecated
 
267
  * @deprecated
268
  *
269
- * @return string The untitled title.
 
270
  */
271
- public function untitled() {
272
- $tsf = \the_seo_framework();
273
- $tsf->_deprecated_function( 'the_seo_framework()->untitled()', '3.1.0', 'the_seo_framework()->get_static_untitled_title()' );
274
- return $tsf->get_static_untitled_title();
275
  }
276
 
277
  /**
278
- * Adds title pagination, if paginated.
279
  *
280
- * @since 2.6.0
281
- * @since 3.1.0 Deprecated.
 
282
  * @deprecated
283
- *
284
- * @param string $title The current Title.
285
- * @return string Title with maybe pagination added.
286
  */
287
- public function add_title_pagination( $title ) {
 
 
 
288
 
289
- $tsf = \the_seo_framework();
290
- $tsf->_deprecated_function( 'the_seo_framework()->add_title_pagination()', '3.1.0', 'the_seo_framework()->merge_title_pagination()' );
291
-
292
- if ( $this->is_404() || $this->is_admin() || $this->is_preview() )
293
- return $title;
294
- $page = $this->page();
295
- $paged = $this->paged();
296
- if ( $page && $paged ) {
297
- /**
298
- * @since 2.4.3
299
- * Adds page numbering within the title.
300
- */
301
- if ( $paged >= 2 || $page >= 2 ) {
302
- $sep = $this->get_title_separator();
303
- $page_number = max( $paged, $page );
304
- /**
305
- * @since 2.9.4
306
- * @param string $pagination The pagination addition.
307
- * @param string $title The old title.
308
- * @param int $page_number The page number.
309
- * @param string $sep The separator used.
310
- */
311
- $pagination = \apply_filters_ref_array(
312
- 'the_seo_framework_title_pagination',
313
- array(
314
- /* translators: %d = page number. Front-end output. */
315
- " $sep " . sprintf( \__( 'Page %d', 'autodescription' ), $page_number ),
316
- $title,
317
- $page_number,
318
- $sep,
319
- )
320
- );
321
- $title .= $pagination;
322
- }
323
- }
324
- return $title;
325
  }
326
 
327
  /**
328
- * Adds the title additions to the title.
329
  *
330
- * @since 2.6.0
331
- * @since 3.1.0 Deprecated.
 
332
  * @deprecated
333
- *
334
- * @param string $title The tite.
335
- * @param string $blogname The blogname.
336
- * @param string $seplocation The separator location.
337
- * @return string Title with possible additions.
338
  */
339
- public function process_title_additions( $title = '', $blogname = '', $seplocation = '' ) {
 
 
 
340
 
341
- $tsf = \the_seo_framework();
342
- $tsf->_deprecated_function( 'the_seo_framework()->process_title_additions()', '3.1.0', 'the_seo_framework()->merge_title_branding()' );
 
 
 
 
 
 
 
 
 
 
 
343
 
344
- $sep = $tsf->get_title_separator();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
 
346
- $title = trim( $title );
347
- $blogname = trim( $blogname );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
 
349
- if ( $blogname && $title ) {
350
- if ( 'left' === $seplocation ) {
351
- $title = $blogname . " $sep " . $title;
352
- } else {
353
- $title = $title . " $sep " . $blogname;
354
- }
355
- }
 
 
 
 
 
 
 
356
 
357
- return $title;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  }
359
 
360
  /**
361
- * Cache current Title in static variable
362
- * Must be called inside the loop
363
  *
364
- * @since 2.2.2
365
- * @since 2.4.0 : If the theme is doing it right, override cache parameters to speed things up.
366
- * @staticvar array $title_cache
 
 
 
 
 
367
  *
368
- * @param string $title The Title to return
369
- * @param string $sep The Title sepeartor
370
- * @param string $seplocation The Title sepeartor location, accepts 'left' or 'right'.
371
- * @param bool $meta Ignore theme doing it wrong.
372
- * @return string The title
373
  */
374
- public function title_from_cache( $title = '', $sep = '', $seplocation = '', $meta = false ) {
375
- $tsf = \the_seo_framework();
376
- $tsf->_deprecated_function( 'the_seo_framework()->title_from_cache()', '3.1.0', 'the_seo_framework()->get_title(...)' );
377
- return $meta ? $tsf->get_open_graph_title() : $tsf->get_title();
378
  }
379
 
380
  /**
381
- * Fetches single term title.
 
382
  *
383
- * @since 2.6.0
384
- * @since 3.1.0 Deprecated.
385
  * @deprecated
386
  *
387
- * @param string $depr Deprecated.
388
- * @param bool $depr Deprecated.
389
- * @param \WP_Term|null $term The WP_Term object.
390
- * @return string Single term title.
391
  */
392
- public function single_term_title( $depr = '', $_depr = true, $term = null ) {
393
- $tsf = \the_seo_framework();
394
- $tsf->_deprecated_function( 'the_seo_framework()->single_term_title()', '3.1.0', 'the_seo_framework()->get_generated_single_term_title()' );
395
- return $tsf->get_generated_single_term_title( $term );
396
  }
397
 
398
  /**
399
- * Returns Post Title from ID.
400
  *
401
  * @since 2.6.0
402
- * @since 3.1.0 Deprecated.
 
403
  * @deprecated
404
  *
405
- * @param int $id The Post ID.
406
- * @param string $title Optional. The current/fallback Title.
407
- * @return string Post Title
408
  */
409
- public function post_title_from_ID( $id = 0, $title = '' ) { // phpcs:ignore -- ID is capitalized because WordPress does that too: get_the_ID().
 
410
  $tsf = \the_seo_framework();
411
- $tsf->_deprecated_function( 'the_seo_framework()->post_title_from_ID()', '3.1.0', 'the_seo_framework()->get_raw_generated_title( [ \'id\' => $id ] )' );
412
 
413
- if ( $tsf->is_archive() )
414
- return $title;
 
 
 
 
 
 
 
 
415
 
416
- return $tsf->get_raw_generated_title( [ 'id' => $id ] ) ?: $title;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  }
418
 
419
  /**
420
- * Gets the title from custom field
 
 
 
421
  *
422
- * @since 2.2.8
423
- * @since 3.1.0 Deprecated.
424
  * @deprecated
 
425
  *
426
- * @param string $title the fallback title.
427
- * @param bool $escape Parse Title through saninitation calls.
428
- * @param int $id The Post ID.
429
- * @param string $taxonomy The term name.
430
- * @return string The Title.
431
  */
432
- public function title_from_custom_field( $title = '', $escape = false, $id = null, $taxonomy = null ) {
 
433
  $tsf = \the_seo_framework();
434
- $tsf->_deprecated_function( 'the_seo_framework()->title_from_custom_field()', '3.1.0', 'the_seo_framework()->get_raw_custom_field_title()' );
435
 
436
- $id = isset( $id ) ? $id : $tsf->get_the_real_ID();
437
 
438
- $title = $tsf->get_raw_custom_field_title( [
439
- 'id' => $id,
440
- 'taxonomy' => $taxonomy,
441
- ] );
 
 
 
 
 
 
 
 
 
442
 
443
- return $escape ? $tsf->escape_title( $title, false ) : (string) $title;
 
 
 
 
 
 
 
444
  }
445
 
446
  /**
447
- * Fetch Tax labels
448
  *
449
- * @since 2.3.1
450
- * @since 3.1.0 Deprecated
 
 
 
 
 
 
 
451
  * @deprecated
452
- * @staticvar object $labels
453
  *
454
- * @param string $tax_type the Taxonomy type.
455
- * @return object|null with all the labels as member variables
 
456
  */
457
- public function get_tax_labels( $tax_type ) {
 
 
 
 
 
 
458
 
459
- \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_tax_labels()', '3.1.0' );
 
460
 
461
- static $labels = null;
 
 
 
 
 
 
 
 
 
 
 
 
462
 
463
- if ( isset( $labels ) )
464
- return $labels;
465
 
466
- $tax_object = \get_taxonomy( $tax_type );
467
 
468
- if ( is_object( $tax_object ) )
469
- return $labels = (object) $tax_object->labels;
 
 
 
470
 
471
- //* Nothing found.
472
- return null;
473
  }
474
 
475
  /**
476
- * Checks (current) Post Type for if this plugin may use it for customizable SEO.
477
  *
478
- * @since 2.6.0
479
- * @since 2.9.3 : Improved caching structure. i.e. it's faster now when no $post_type is supplied.
480
- * @staticvar array $cache
481
- * @since 3.1.0 1. Deprecated
482
- * 2. First parameter is implied.
483
- * @global \WP_Screen $current_screen
484
  *
485
- * @param bool $public Whether to only get Public Post types.
486
- * @param string $post_type Optional. The post type to check.
487
- * @return bool|string The allowed Post Type. False if it's not supported.
488
  */
489
- public function get_supported_post_type( $public = true, $post_type = '' ) {
 
490
  $tsf = \the_seo_framework();
491
- $tsf->_deprecated_function( 'the_seo_framework()->get_supported_post_type()', '3.1.0', 'the_seo_framework()->is_post_type_supported()' );
492
- return $tsf->is_post_type_supported( $post_type ) ? $post_type : false;
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  }
494
 
495
  /**
496
- * Returns the special URL of a paged post.
497
  *
498
- * Taken from _wp_link_page() in WordPress core, but instead of anchor markup, just return the URL.
499
- *
500
- * @since 2.2.4
501
- * @since 3.0.0 Now uses WordPress permalinks.
502
- * @since 3.1.0 Deprecated.
 
503
  *
504
- * @param int $i The page number to generate the URL from.
505
- * @param int $post_id The post ID.
506
- * @param string $pos Which url to get, accepts next|prev.
507
- * @return string The unescaped paged URL.
508
  */
509
- public function get_paged_post_url( $i, $post_id = 0, $pos = 'prev' ) {
 
510
  $tsf = \the_seo_framework();
511
- $tsf->_deprecated_function( 'the_seo_framework()->get_paged_post_url()', '3.1.0', 'the_seo_framework()->get_paged_url()' );
512
 
513
- if ( empty( $post_id ) )
514
- $post_id = $tsf->get_the_real_ID();
515
 
516
- if ( 1 === $i ) :
517
- $url = \get_permalink( $post_id );
518
- else :
519
- $post = \get_post( $post_id );
520
- $url = \get_permalink( $post_id );
521
 
522
- if ( $i >= 2 ) {
523
- //* Fix adding pagination url.
524
 
525
- //* Parse query arg, put in var and remove from current URL.
526
- $query_arg = parse_url( $url, PHP_URL_QUERY );
527
- if ( isset( $query_arg ) )
528
- $url = str_replace( '?' . $query_arg, '', $url );
529
 
530
- //* Continue if still bigger than or equal to 2.
531
- if ( $i >= 2 ) {
532
- // Calculate current page number.
533
- $_current = 'next' === $pos ? (string) ( $i - 1 ) : (string) ( $i + 1 );
534
 
535
- //* We're adding a page.
536
- $_last_occurrence = strrpos( $url, '/' . $_current . '/' );
 
 
 
 
 
 
 
 
 
 
537
 
538
- if ( false !== $_last_occurrence )
539
- $url = substr_replace( $url, '/', $_last_occurrence, strlen( '/' . $_current . '/' ) );
540
- }
541
- }
 
 
 
 
542
 
543
- if ( ! $tsf->pretty_permalinks || $tsf->is_draft( $post ) ) {
 
544
 
545
- //* Put removed query arg back prior to adding pagination.
546
- if ( isset( $query_arg ) )
547
- $url = $url . '?' . $query_arg;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
548
 
549
- $url = \add_query_arg( 'page', $i, $url );
550
- } elseif ( $tsf->is_static_frontpage( $post_id ) ) {
551
- global $wp_rewrite;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
552
 
553
- $url = \trailingslashit( $url ) . \user_trailingslashit( $wp_rewrite->pagination_base . '/' . $i, 'single_paged' );
 
 
 
 
 
 
 
 
 
 
554
 
555
- //* Add back query arg if removed.
556
- if ( isset( $query_arg ) )
557
- $url = $url . '?' . $query_arg;
558
- } else {
559
- $url = \trailingslashit( $url ) . \user_trailingslashit( $i, 'single_paged' );
560
 
561
- //* Add back query arg if removed.
562
- if ( isset( $query_arg ) )
563
- $url = $url . '?' . $query_arg;
564
  }
565
- endif;
566
 
567
- return $url;
568
  }
569
 
570
  /**
571
- * Checks if the string input is exactly '1'.
 
572
  *
573
- * @since 2.6.0
574
- * @since 3.1.0 Deprecated.
 
 
575
  *
576
- * @param string $value The value to check.
577
- * @return bool true if value is '1'
578
  */
579
- public function is_checked( $value ) {
580
- $tsf = \the_seo_framework();
581
- $tsf->_deprecated_function( 'the_seo_framework()->is_checked()', '3.1.0' );
582
- return (bool) $value;
583
  }
584
 
585
  /**
586
- * Checks if the option is used and checked.
587
  *
588
- * @since 2.6.0
589
- * @since 3.1.0 Deprecated.
 
 
590
  *
591
- * @param string $option The option name.
592
- * @return bool Option is checked.
593
  */
594
- public function is_option_checked( $option ) {
595
- $tsf = \the_seo_framework();
596
- $tsf->_deprecated_function( 'the_seo_framework()->is_option_checked()', '3.1.0' );
597
- return (bool) $tsf->get_option( $option );
 
 
 
598
  }
599
 
600
  /**
601
- * Returns the custom user-inputted description.
602
  *
603
- * @since 1.0.0
604
- * @since 2.9.0 Added two filters.
605
- * @since 3.0.6 Silently deprecated.
606
- * @since 3.1.0 Deprecated.
607
- * @deprecated Use `get_description()` instead.
608
- * @deprecated Use `get_generated_description()` instead.
609
  *
610
- * @param array|null $args An array of 'id' and 'taxonomy' values.
611
- * Accepts int values for backward compatibility.
612
- * @param bool $escape Whether to escape the description.
613
- * @return string The description
614
  */
615
- public function generate_description( $description = '', $args = null, $escape ) {
616
- $tsf = \the_seo_framework();
617
- $tsf->_deprecated_function( 'the_seo_framework()->generate_description()', '3.1.0', 'the_seo_framework()->get_description()' );
618
- return $tsf->get_description( $args, $escape ) ?: $description;
619
  }
620
 
621
  /**
622
- * Creates description from custom fields.
623
  *
624
- * @since 2.4.1
625
- * @since 3.0.6 Silently deprecated.
626
- * @since 3.1.0 Deprecated.
627
- * @deprecated Use `get_description_from_custom_field()` instead.
628
  *
629
- * @param array $args description args : {
630
- * @param int $id the term or page id.
631
- * @param string $taxonomy taxonomy name.
632
- * @param bool $is_home We're generating for the home page.
633
- * }
634
- * @param bool $escape Escape the output if true.
635
- * @return string|mixed The description.
 
636
  */
637
- public function description_from_custom_field( $args = null, $escape = true ) {
638
- $tsf = \the_seo_framework();
639
- $tsf->_deprecated_function( 'the_seo_framework()->description_from_custom_field()', '3.1.0', 'the_seo_framework()->get_description_from_custom_field()' );
640
- return $tsf->get_description_from_custom_field( $args, $escape );
641
  }
642
 
643
  /**
644
- * Generates description from content while parsing filters.
645
  *
646
- * @since 2.3.3
647
- * @since 3.0.0 No longer checks for protected posts.
648
- * Check is moved to $this->generate_the_description().
649
- * @since 3.0.6 Silently deprecated.
650
- * @since 3.1.0 Now listens to the `auto_description` option.
651
- * @deprecated Use `get_generated_description()` instead.
652
  *
653
- * @param array $args description args : {
654
- * @param int $id the term or page id.
655
- * @param string $taxonomy taxonomy name.
656
- * @param bool $is_home Whether we're generating for the home page.
657
- * @param bool $get_custom_field Do not fetch custom title when false.
658
- * @param bool $social Generate Social Description when true.
659
- * }
660
- * @param bool $escape Escape output when true.
661
- * @return string $output The description.
662
  */
663
- public function generate_description_from_id( $args = null, $escape = true ) {
664
- $tsf = \the_seo_framework();
665
- $tsf->_deprecated_function( 'the_seo_framework()->generate_description_from_id()', '3.1.0', 'the_seo_framework()->get_generated_description()' );
666
- return $tsf->get_generated_description( $args, $escape );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
  }
668
  }
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;
32
  *
33
  * @since 2.8.0
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
  * @ignore
37
  */
38
  final class Deprecated {
43
  public function __construct() { }
44
 
45
  /**
46
+ * Returns a filterable sequential array of default scripts.
47
  *
48
+ * @since 3.2.2
49
+ * @since 4.0.0 Deprecated.
50
  * @deprecated
 
 
51
  *
52
+ * @return array
 
53
  */
54
+ public function get_default_scripts() {
55
 
56
  $tsf = \the_seo_framework();
57
+ $tsf->_deprecated_function( 'the_seo_framework()->get_default_scripts()', '4.0.0' );
58
+
59
+ return array_merge(
60
+ \The_SEO_Framework\Bridges\Scripts::get_tsf_scripts(),
61
+ \The_SEO_Framework\Bridges\Scripts::get_tt_scripts()
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Enqueues Gutenberg-related scripts.
67
+ *
68
+ * @since 3.2.0
69
+ * @since 4.0.0 Deprecated.
70
+ * @deprecated
71
+ *
72
+ * @return void Early if already enqueued.
73
+ */
74
+ public function enqueue_gutenberg_compat_scripts() {
75
 
76
+ $tsf = \the_seo_framework();
77
+ $tsf->_deprecated_function( 'the_seo_framework()->enqueue_gutenberg_compat_scripts()', '4.0.0' );
 
 
 
78
 
79
+ if ( \The_SEO_Framework\_has_run( __METHOD__ ) ) return;
 
 
 
 
 
80
 
81
+ \The_SEO_Framework\Builders\Scripts::register(
82
+ \The_SEO_Framework\Bridges\Scripts::get_gutenberg_compat_scripts()
83
+ );
84
  }
85
 
86
  /**
87
+ * Enqueues Media Upload and Cropping scripts.
 
88
  *
89
+ * @since 3.1.0
90
+ * @since 4.0.0 Deprecated.
 
 
91
  * @deprecated
 
92
  *
93
+ * @return void Early if already enqueued.
94
  */
95
+ public function enqueue_media_scripts() {
96
+
97
  $tsf = \the_seo_framework();
98
+ $tsf->_deprecated_function( 'the_seo_framework()->enqueue_media_scripts()', '4.0.0' );
99
+
100
+ if ( \The_SEO_Framework\_has_run( __METHOD__ ) ) return;
101
+
102
+ $args = [];
103
+ if ( $tsf->is_post_edit() ) {
104
+ $args['post'] = $tsf->get_the_real_admin_ID();
105
+ }
106
+ \wp_enqueue_media( $args );
107
+
108
+ \The_SEO_Framework\Builders\Scripts::register(
109
+ \The_SEO_Framework\Bridges\Scripts::get_media_scripts()
110
+ );
111
  }
112
 
113
  /**
114
+ * Enqueues Primary Term Selection scripts.
 
115
  *
116
+ * @since 3.1.0
117
+ * @since 4.0.0 Deprecated.
118
  * @deprecated
 
 
 
119
  *
120
+ * @return void Early if already enqueued.
 
121
  */
122
+ public function enqueue_primaryterm_scripts() {
123
+
124
  $tsf = \the_seo_framework();
125
+ $tsf->_deprecated_function( 'the_seo_framework()->enqueue_primaryterm_scripts()', '4.0.0' );
126
+
127
+ if ( \The_SEO_Framework\_has_run( __METHOD__ ) ) return;
128
+
129
+ \The_SEO_Framework\Builders\Scripts::register(
130
+ \The_SEO_Framework\Bridges\Scripts::get_primaryterm_scripts()
131
+ );
132
  }
133
 
134
  /**
135
+ * Includes the necessary sortable metabox scripts.
 
136
  *
137
+ * @since 2.2.2
138
+ * @since 4.0.0 Deprecated.
 
 
139
  * @deprecated
140
+ */
141
+ public function metabox_scripts() {
142
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->metabox_scripts()', '4.0.0', '\The_SEO_Framework\Bridges\Scripts::prepare_metabox_scripts()' );
143
+ \The_SEO_Framework\Bridges\Scripts::prepare_metabox_scripts();
144
+ }
145
+
146
+ /**
147
+ * Returns the SEO Bar.
148
  *
149
+ * @since 3.0.4
150
+ * @since 4.0.0 Deprecated
151
+ * @staticvar string $type
152
+ * @deprecated
153
  *
154
+ * @param string $column the current column : If it's a taxonomy, this is empty
155
+ * @param int $post_id the post id : If it's a taxonomy, this is the column name
156
+ * @param string $tax_id this is empty : If it's a taxonomy, this is the taxonomy id
 
 
 
 
 
 
 
 
 
 
 
157
  */
158
+ public function get_seo_bar( $column, $post_id, $tax_id ) {
159
 
160
  $tsf = \the_seo_framework();
161
+ $tsf->_deprecated_function( 'the_seo_framework()->post_status()', '4.0.0', 'the_seo_framework()->get_generated_seo_bar()' );
 
 
 
 
162
 
163
+ $type = \get_post_type( $post_id );
164
+
165
+ if ( false === $type || '' !== $tax_id ) {
166
+ $type = $tsf->get_current_taxonomy();
 
 
 
167
  }
168
+
169
+ if ( '' !== $tax_id ) {
170
+ $column = $post_id;
171
+ $post_id = $tax_id;
172
  }
173
 
174
+ return $tsf->post_status( $post_id, $type );
175
  }
176
 
177
  /**
178
+ * Renders post status. Caches the output.
179
  *
180
+ * @since 2.1.9
181
+ * @staticvar string $post_i18n The post type slug.
182
+ * @staticvar bool $is_term If we're dealing with TT pages.
183
+ * @since 2.8.0 Third parameter `$echo` has been put into effect.
184
+ * @since 4.0.0 Deprecated.
185
  * @deprecated
186
  *
187
+ * @param int $post_id The Post ID or taxonomy ID.
188
+ * @param string $type The content type.
189
+ * @param bool $echo Whether to echo the value. Does not eliminate return.
190
+ * @return string|void $content The post SEO status. Void if $echo is true.
 
 
 
 
 
 
 
191
  */
192
+ public function post_status( $post_id, $type = '', $echo = false ) {
193
+
194
+ $tsf = \the_seo_framework();
195
+
196
+ $tsf->_deprecated_function( 'the_seo_framework()->post_status()', '4.0.0', 'the_seo_framework()->get_generated_seo_bar()' );
197
+
198
+ if ( ! $post_id )
199
+ $post_id = $tsf->get_the_real_ID();
200
+
201
+ if ( 'inpost' === $type || ! $type ) {
202
+ $type = \get_post_type( $post_id );
203
+ }
204
+
205
+ if ( $tsf->is_post_type_page( $type ) ) {
206
+ $post_type = $type;
207
+ } else {
208
+ $taxonomy = $tsf->get_current_taxonomy();
209
+ $post_type = $tsf->get_admin_post_type();
210
+ }
211
+
212
+ $bar = $tsf->get_generated_seo_bar( [
213
+ 'id' => $post_id,
214
+ 'post_type' => $post_type,
215
+ 'taxonomy' => $taxonomy,
216
+ ] );
217
+
218
+ if ( $echo ) {
219
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- the SEO Bar is escaped.
220
+ echo $bar;
221
+ } else {
222
+ return $bar;
223
+ }
224
  }
225
 
226
  /**
227
+ * Returns the static scripts class object.
228
  *
229
+ * The first letter of the method is capitalized, to indicate it's a class caller.
230
+ *
231
+ * @since 3.1.0
232
+ * @since 4.0.0 Deprecated.
233
  * @deprecated
234
  *
235
+ * @return string The scripts class name.
 
 
 
 
 
 
 
 
 
 
236
  */
237
+ public function Scripts() { // phpcs:ignore, WordPress.NamingConventions.ValidFunctionName
238
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->Scripts()', '4.0.0', '\The_SEO_Framework\Builders\Scripts::class' );
239
+ return \The_SEO_Framework\Builders\Scripts::class;
 
 
 
 
 
 
240
  }
241
 
242
  /**
243
+ * Determines if we're doing ajax.
 
 
 
244
  *
245
+ * @since 2.9.0
246
+ * @since 4.0.0 1. Now uses wp_doing_ajax()
247
+ * 2. Deprecated.
 
 
248
  * @deprecated
249
  *
250
+ * @return bool True if AJAX
 
 
251
  */
252
+ public function doing_ajax() {
253
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->doing_ajax()', '4.0.0', 'wp_doing_ajax' );
254
+ return \wp_doing_ajax();
 
255
  }
256
 
257
  /**
258
+ * Whether to lowercase the noun or keep it UCfirst.
259
+ * Depending if language is German.
260
  *
261
  * @since 2.6.0
262
+ * @since 4.0.0 Deprecated
 
263
  * @deprecated
264
+ * @staticvar array $lowercase Contains nouns.
265
  *
266
+ * @param string $noun The noun to lowercase.
267
+ * @return string The maybe lowercase noun.
268
  */
269
+ public function maybe_lowercase_noun( $noun ) {
270
+
271
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->maybe_lowercase_noun()', '4.0.0' );
272
+
273
+ static $lowercase = [];
274
+
275
+ if ( isset( $lowercase[ $noun ] ) )
276
+ return $lowercase[ $noun ];
277
+
278
+ return $lowercase[ $noun ] = \the_seo_framework()->check_wp_locale( 'de' ) ? $noun : strtolower( $noun );
279
  }
280
 
281
  /**
282
+ * Detect WordPress language.
283
+ * Considers en_UK, en_US, en, etc.
284
  *
285
  * @since 2.6.0
286
+ * @since 3.1.0 Removed caching.
287
+ * @since 4.0.0 Deprecated.
288
  * @deprecated
289
  *
290
+ * @param string $locale Required, the locale.
291
+ * @return bool Whether the input $locale is in the current WordPress locale.
292
  */
293
+ public function check_wp_locale( $locale = '' ) {
294
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->check_wp_locale()', '4.0.0' );
295
+ return false !== strpos( \get_locale(), $locale );
 
296
  }
297
 
298
  /**
299
+ * Initializes term meta data filters and functions.
300
  *
301
+ * @since 2.7.0
302
+ * @since 3.0.0 No longer checks for admin query.
303
+ * @since 4.0.0 Deprecated.
304
  * @deprecated
 
 
 
305
  */
306
+ public function initialize_term_meta() {
307
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->initialize_term_meta()', '4.0.0', '\the_seo_framework()->init_term_meta()' );
308
+ \the_seo_framework()->init_term_meta();
309
+ }
310
 
311
+ /**
312
+ * Ping search engines on post publish.
313
+ *
314
+ * @since 2.2.9
315
+ * @since 2.8.0 Only worked when the blog was not public...
316
+ * @since 3.1.0 Now allows one ping per language.
317
+ * @uses $this->add_cache_key_suffix()
318
+ * @since 3.2.3 1. Now works as intended again.
319
+ * 2. Removed Easter egg.
320
+ * @since 4.0.0 Deprecated.
321
+ * @deprecated
322
+ *
323
+ * @return void Early if blog is not public.
324
+ */
325
+ public static function ping_searchengines() {
326
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->ping_searchengines()', '4.0.0', '\The_SEO_Framework\Bridges\Ping::ping_search_engines()' );
327
+ \The_SEO_Framework\Bridges\Ping::ping_search_engines();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  }
329
 
330
  /**
331
+ * Pings the sitemap location to Google.
332
  *
333
+ * @since 2.2.9
334
+ * @since 3.1.0 Updated ping URL. Old one still worked, too.
335
+ * @since 4.0.0 Deprecated.
336
  * @deprecated
337
+ * @link https://support.google.com/webmasters/answer/6065812?hl=en
 
 
 
 
338
  */
339
+ public static function ping_google() {
340
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->ping_google()', '4.0.0', '\The_SEO_Framework\Bridges\Ping::ping_google()' );
341
+ \The_SEO_Framework\Bridges\Ping::ping_google();
342
+ }
343
 
344
+ /**
345
+ * Pings the sitemap location to Bing.
346
+ *
347
+ * @since 2.2.9
348
+ * @since 3.2.3 Updated ping URL. Old one still worked, too.
349
+ * @since 4.0.0 Deprecated.
350
+ * @deprecated
351
+ * @link https://www.bing.com/webmaster/help/how-to-submit-sitemaps-82a15bd4
352
+ */
353
+ public static function ping_bing() {
354
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->ping_bing()', '4.0.0', '\The_SEO_Framework\Bridges\Ping::ping_bing()' );
355
+ \The_SEO_Framework\Bridges\Ping::ping_bing();
356
+ }
357
 
358
+ /**
359
+ * Returns the stylesheet XSL location URL.
360
+ *
361
+ * @since 2.8.0
362
+ * @since 3.0.0 1: No longer uses home URL from cache. But now uses `get_home_url()`.
363
+ * 2: Now takes query parameters (if any) and restores them correctly.
364
+ * @since 4.0.0 Deprecated.
365
+ * @deprecated
366
+ * @global \WP_Rewrite $wp_rewrite
367
+ *
368
+ * @return string URL location of the XSL stylesheet. Unescaped.
369
+ */
370
+ public function get_sitemap_xsl_url() {
371
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_sitemap_xsl_url()', '4.0.0', '\The_SEO_Framework\Bridges\Sitemap::get_instance()->get_expected_sitemap_endpoint_url(\'xsl-stylesheet\')' );
372
+ return \The_SEO_Framework\Bridges\Sitemap::get_instance()->get_expected_sitemap_endpoint_url( 'xsl-stylesheet' );
373
+ }
374
 
375
+ /**
376
+ * Returns the sitemap XML location URL.
377
+ *
378
+ * @since 2.9.2
379
+ * @since 3.0.0 1: No longer uses home URL from cache. But now uses `get_home_url()`.
380
+ * 2: Now takes query parameters (if any) and restores them correctly.
381
+ * @since 4.0.0 Deprecated.
382
+ * @deprecated
383
+ * @global \WP_Rewrite $wp_rewrite
384
+ *
385
+ * @return string URL location of the XML sitemap. Unescaped.
386
+ */
387
+ public function get_sitemap_xml_url() {
388
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_sitemap_xml_url()', '4.0.0', '\The_SEO_Framework\Bridges\Sitemap::get_instance()->get_expected_sitemap_endpoint_url()' );
389
+ return \The_SEO_Framework\Bridges\Sitemap::get_instance()->get_expected_sitemap_endpoint_url();
390
+ }
391
 
392
+ /**
393
+ * Sitemap XSL stylesheet output.
394
+ *
395
+ * @since 2.8.0
396
+ * @since 3.1.0 1. Now outputs 200-response code.
397
+ * 2. Now outputs robots tag, preventing indexing.
398
+ * 3. Now overrides other header tags.
399
+ * @since 4.0.0 Deprecated.
400
+ * @deprecated
401
+ */
402
+ public function output_sitemap_xsl_stylesheet() {
403
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->output_sitemap_xsl_stylesheet()', '4.0.0' );
404
+ return \The_SEO_Framework\Bridges\Sitemap::get_instance()->output_stylesheet();
405
+ }
406
 
407
+ /**
408
+ * Determines if post type supports The SEO Framework.
409
+ *
410
+ * @since 2.3.9
411
+ * @since 3.1.0 1. Removed caching.
412
+ * 2. Now works in admin.
413
+ * @since 4.0.0 Deprecated.
414
+ * @deprecated
415
+ *
416
+ * @param string $post_type Optional. The post type to check.
417
+ * @return bool true of post type is supported.
418
+ */
419
+ public function post_type_supports_custom_seo( $post_type = '' ) {
420
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->post_type_supports_custom_seo()', '4.0.0', 'the_seo_framework()->is_post_type_supported()' );
421
+ return \the_seo_framework()->is_post_type_supported( $post_type );
422
  }
423
 
424
  /**
425
+ * Determines if the taxonomy supports The SEO Framework.
 
426
  *
427
+ * Checks if at least one taxonomy objects post type supports The SEO Framework,
428
+ * and wether the taxonomy is public and rewritable.
429
+ *
430
+ * @since 3.1.0
431
+ * @since 4.0.0 1. Now goes over all post types for the taxonomy.
432
+ * 2. Can now return true if at least one post type for the taxonomy is supported.
433
+ * 3. Deprecated.
434
+ * @deprecated
435
  *
436
+ * @param string $taxonomy Optional. The taxonomy name.
437
+ * @return bool True if at least one post type in taxonomy isn't disabled.
 
 
 
438
  */
439
+ public function taxonomy_supports_custom_seo( $taxonomy = '' ) {
440
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->taxonomy_supports_custom_seo()', '4.0.0', 'the_seo_framework()->is_taxonomy_supported()' );
441
+ return \the_seo_framework()->is_taxonomy_supported( $taxonomy );
 
442
  }
443
 
444
  /**
445
+ * Returns taxonomical canonical URL.
446
+ * Automatically adds pagination if the ID matches the query.
447
  *
448
+ * @since 3.0.0
449
+ * @since 4.0.0 Deprecated
450
  * @deprecated
451
  *
452
+ * @param int $term_id The term ID.
453
+ * @param string $taxonomy The taxonomy.
454
+ * @return string The taxonomical canonical URL, if any.
 
455
  */
456
+ public function get_taxonomial_canonical_url( $term_id, $taxonomy ) {
457
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_taxonomial_canonical_url()', '4.0.0', 'the_seo_framework()->get_taxonomical_canonical_url()' );
458
+ return \the_seo_framework()->get_taxonomical_canonical_url( $term_id, $taxonomy );
 
459
  }
460
 
461
  /**
462
+ * Tries to fetch a term by $id from query.
463
  *
464
  * @since 2.6.0
465
+ * @since 3.0.0 Can now get custom post type objects.
466
+ * @since 4.0.0 Deprecated.
467
  * @deprecated
468
  *
469
+ * @param int $id The possible taxonomy Term ID.
470
+ * @return false|object The Term object.
 
471
  */
472
+ public function fetch_the_term( $id = '' ) {
473
+
474
  $tsf = \the_seo_framework();
 
475
 
476
+ $tsf->_deprecated_function( 'the_seo_framework()->fetch_the_term()', '4.0.0' );
477
+
478
+ static $term = [];
479
+
480
+ if ( isset( $term[ $id ] ) )
481
+ return $term[ $id ];
482
+
483
+ //* Return null if no term can be detected.
484
+ if ( false === $tsf->is_archive() )
485
+ return false;
486
 
487
+ if ( \is_admin() ) {
488
+ $taxonomy = $tsf->get_current_taxonomy();
489
+ if ( $taxonomy ) {
490
+ $term_id = $id ?: $tsf->get_the_real_admin_ID();
491
+ $term[ $id ] = \get_term( $term_id, $taxonomy );
492
+ }
493
+ } else {
494
+ if ( $tsf->is_category() || $tsf->is_tag() ) {
495
+ $term[ $id ] = \get_queried_object();
496
+ } elseif ( $tsf->is_tax() ) {
497
+ $term[ $id ] = \get_term_by( 'slug', \get_query_var( 'term' ), \get_query_var( 'taxonomy' ) );
498
+ } elseif ( \is_post_type_archive() ) {
499
+ $post_type = \get_query_var( 'post_type' );
500
+ $post_type = is_array( $post_type ) ? reset( $post_type ) : $post_type;
501
+
502
+ $term[ $id ] = \get_post_type_object( $post_type );
503
+ }
504
+ }
505
+
506
+ if ( isset( $term[ $id ] ) )
507
+ return $term[ $id ];
508
+
509
+ return $term[ $id ] = false;
510
  }
511
 
512
  /**
513
+ * Return custom field post meta data.
514
+ *
515
+ * Return only the first value of custom field. Return false if field is
516
+ * blank or not set.
517
  *
518
+ * @since 2.0.0
519
+ * @since 4.0.0 Deprecated
520
  * @deprecated
521
+ * @staticvar array $field_cache
522
  *
523
+ * @param string $field Custom field key.
524
+ * @param int $post_id The post ID.
525
+ * @return mixed|boolean Return value or false on failure.
 
 
526
  */
527
+ public function get_custom_field( $field, $post_id = null ) {
528
+
529
  $tsf = \the_seo_framework();
 
530
 
531
+ $tsf->_deprecated_function( 'the_seo_framework()->get_custom_field()', '4.0.0', 'the_seo_framework()->get_post_meta_item()' );
532
 
533
+ //* If field is falsesque, get_post_meta() will return an array.
534
+ if ( ! $field )
535
+ return false;
536
+
537
+ static $field_cache = [];
538
+
539
+ if ( isset( $field_cache[ $field ][ $post_id ] ) )
540
+ return $field_cache[ $field ][ $post_id ];
541
+
542
+ if ( empty( $post_id ) )
543
+ $post_id = $tsf->get_the_real_ID();
544
+
545
+ $custom_field = \get_post_meta( $post_id, $field, true );
546
 
547
+ //* If custom field is empty, empty cache..
548
+ if ( empty( $custom_field ) )
549
+ $field_cache[ $field ][ $post_id ] = '';
550
+
551
+ //* Render custom field, slashes stripped, sanitized if string
552
+ $field_cache[ $field ][ $post_id ] = is_array( $custom_field ) ? \stripslashes_deep( $custom_field ) : stripslashes( $custom_field );
553
+
554
+ return $field_cache[ $field ][ $post_id ];
555
  }
556
 
557
  /**
558
+ * Returns image URL suitable for Schema items.
559
  *
560
+ * These are images that are strictly assigned to the Post or Page, fallbacks are omitted.
561
+ * Themes should compliment these. If not, then Open Graph should at least
562
+ * compliment these.
563
+ * If that's not even true, then I don't know what happens. But then you're
564
+ * in a grey area... @TODO make images optional for Schema?
565
+ *
566
+ * @since 2.9.3
567
+ * @since 3.2.2 No longer relies on the query.
568
+ * @since 4.0.0 Deprecated.
569
  * @deprecated
 
570
  *
571
+ * @param int|string $id The page, post, product or term ID.
572
+ * @param bool $singular Whether the ID is singular or archival.
573
+ * @return string|array $url The Schema.org safe image.
574
  */
575
+ public function get_schema_image( $id = 0, $singular = false ) {
576
+
577
+ $tsf = \the_seo_framework();
578
+
579
+ $tsf->_deprecated_function( 'the_seo_framework()->get_schema_image()', '4.0.0', 'the_seo_framework()->get_safe_schema_image()' );
580
+
581
+ if ( ! $singular ) return '';
582
 
583
+ return $tsf->get_safe_schema_image( $id ?: null, false );
584
+ }
585
 
586
+ /**
587
+ * Returns social image URL.
588
+ *
589
+ * @since 2.9.0
590
+ * @since 3.0.6 Added attachment page compatibility.
591
+ * @since 3.2.2 Now skips the singular meta images on archives.
592
+ * @since 4.0.0 Deprecated.
593
+ * @deprecated
594
+ *
595
+ * @param array $args The image arguments.
596
+ * @return string The social image.
597
+ */
598
+ public function get_social_image( $args = [] ) {
599
 
600
+ $tsf = \the_seo_framework();
 
601
 
602
+ $tsf->_deprecated_function( 'the_seo_framework()->get_social_image()', '4.0.0', 'the_seo_framework()->get_image_from_cache()' );
603
 
604
+ if ( isset( $args['post_id'] ) && $args['post_id'] ) {
605
+ $image = current( $tsf->get_image_details( [ 'id' => $args['post_id'] ], true ) );
606
+ } else {
607
+ $image = current( $tsf->get_image_details( null, true ) );
608
+ }
609
 
610
+ return isset( $image['url'] ) ? $image['url'] : '';
 
611
  }
612
 
613
  /**
614
+ * Returns unescaped HomePage settings image URL from post ID input.
615
  *
616
+ * @since 2.9.0
617
+ * @since 2.9.4 Now converts URL scheme.
618
+ * @since 4.0.0 Deprecated.
619
+ * @deprecated
 
 
620
  *
621
+ * @param int $id The post ID.
622
+ * @return string The unescaped HomePage social image URL.
 
623
  */
624
+ public function get_social_image_url_from_home_meta( $id = 0 ) {
625
+
626
  $tsf = \the_seo_framework();
627
+
628
+ $tsf->_deprecated_function( 'the_seo_framework()->get_social_image_url_from_home_meta()', '4.0.0', "the_seo_framework()->get_option( 'homepage_social_image_url' )" );
629
+
630
+ if ( false === $tsf->is_front_page_by_id( $id ) )
631
+ return '';
632
+
633
+ $src = $tsf->get_option( 'homepage_social_image_url' );
634
+
635
+ if ( ! $src )
636
+ return '';
637
+
638
+ if ( $src && $tsf->matches_this_domain( $src ) )
639
+ $src = $tsf->set_preferred_url_scheme( $src );
640
+
641
+ return $src;
642
  }
643
 
644
  /**
645
+ * Returns unescaped Post settings image URL from post ID input.
646
  *
647
+ * @since 2.8.0
648
+ * @since 2.9.0 1. The second parameter now works.
649
+ * 2. Fallback image ID has been removed.
650
+ * @since 2.9.4 Now converts URL scheme.
651
+ * @since 4.0.0 Deprecated.
652
+ * @deprecated
653
  *
654
+ * @param int $id The post ID. Required.
655
+ * @return string The unescaped social image URL.
 
 
656
  */
657
+ public function get_social_image_url_from_post_meta( $id ) {
658
+
659
  $tsf = \the_seo_framework();
 
660
 
661
+ $tsf->_deprecated_function( 'the_seo_framework()->get_social_image_url_from_post_meta()', '4.0.0', "the_seo_framework()->get_post_meta_item( '_social_image_url' )" );
 
662
 
663
+ $src = $id ? $tsf->get_post_meta_item( '_social_image_url', $id ) : '';
 
 
 
 
664
 
665
+ if ( ! $src )
666
+ return '';
667
 
668
+ if ( $src && $tsf->matches_this_domain( $src ) )
669
+ $src = $tsf->set_preferred_url_scheme( $src );
 
 
670
 
671
+ return $src;
672
+ }
 
 
673
 
674
+ /**
675
+ * Returns unescaped URL from options input.
676
+ *
677
+ * @since 2.8.2
678
+ * @since 2.9.4 1: Now converts URL scheme.
679
+ * 2: $set_og_dimensions now works.
680
+ * @since 4.0.0 Deprecated
681
+ * @deprecated
682
+ *
683
+ * @return string The unescaped social image fallback URL.
684
+ */
685
+ public function get_social_image_url_from_seo_settings() {
686
 
687
+ $tsf = \the_seo_framework();
688
+
689
+ $tsf->_deprecated_function( 'the_seo_framework()->get_social_image_url_from_seo_settings()', '4.0.0', "the_seo_framework()->get_option( 'social_image_fb_url' )" );
690
+
691
+ $src = $tsf->get_option( 'social_image_fb_url' );
692
+
693
+ if ( $src && $tsf->matches_this_domain( $src ) )
694
+ $src = $tsf->set_preferred_url_scheme( $src );
695
 
696
+ return $src;
697
+ }
698
 
699
+ /**
700
+ * Fetches image from post thumbnail.
701
+ *
702
+ * @since 2.9.0
703
+ * @since 2.9.3 Now supports 4K.
704
+ * @since 2.9.4 Now converts URL scheme.
705
+ * @since 4.0.0 Deprecated.
706
+ * @deprecated
707
+ *
708
+ * @param int $id The post ID. Required.
709
+ * @return string The social image URL.
710
+ */
711
+ public function get_social_image_url_from_post_thumbnail( $id ) {
712
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_social_image_url_from_post_thumbnail()', '4.0.0' );
713
+ return \The_SEO_Framework\Builders\Images::get_featured_image_details(
714
+ [
715
+ 'id' => $id,
716
+ 'taxonomy' => '',
717
+ ]
718
+ )->current()['url'];
719
+ }
720
 
721
+ /**
722
+ * Returns the social image URL from an attachment page.
723
+ *
724
+ * @since 3.0.6
725
+ * @since 4.0.0 Deprecated.
726
+ * @deprecated
727
+ *
728
+ * @param int $id The post ID. Required.
729
+ * @return string The attachment URL.
730
+ */
731
+ public function get_social_image_url_from_attachment( $id ) {
732
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_social_image_url_from_attachment()', '4.0.0' );
733
+ return \The_SEO_Framework\Builders\Images::get_attachment_image_details(
734
+ [
735
+ 'id' => $id,
736
+ 'taxonomy' => '',
737
+ ]
738
+ )->current()['url'];
739
+ }
740
 
741
+ /**
742
+ * Fetches images id's from WooCommerce gallery
743
+ *
744
+ * @since 2.5.0
745
+ * @since 4.0.0 Deprecated.
746
+ * @deprecated
747
+ *
748
+ * @return array The image URL's.
749
+ */
750
+ public function get_image_from_woocommerce_gallery() {
751
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_image_from_woocommerce_gallery()', '4.0.0' );
752
 
753
+ $ids = [];
 
 
 
 
754
 
755
+ if ( function_exists( '\\The_SEO_Framework\\_get_product_gallery_image_details' ) ) {
756
+ foreach ( \The_SEO_Framework\_get_product_gallery_image_details() as $details ) {
757
+ $ids[] = $details['id'];
758
  }
759
+ }
760
 
761
+ return $ids;
762
  }
763
 
764
  /**
765
+ * Returns header image URL.
766
+ * Also sets image dimensions. Falls back to current post ID for index.
767
  *
768
+ * @since 2.7.0
769
+ * @since 3.0.0 Now sets preferred canonical URL scheme.
770
+ * @since 4.0.0 Deprecated.
771
+ * @deprecated
772
  *
773
+ * @return string The header image URL, not escaped.
 
774
  */
775
+ public function get_header_image() {
776
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_header_image()', '4.0.0' );
777
+ return \The_SEO_Framework\Builders\Images::get_theme_header_image_details()->current()['url'];
 
778
  }
779
 
780
  /**
781
+ * Fetches site icon brought in WordPress 4.3
782
  *
783
+ * @since 2.8.0
784
+ * @since 3.0.0 : Now sets preferred canonical URL scheme.
785
+ * @since 4.0.0 Deprecated.
786
+ * @deprecated
787
  *
788
+ * @param string|int $size The icon size, accepts 'full' and pixel values.
789
+ * @return string URL site icon, not escaped.
790
  */
791
+ public function get_site_icon( $size = 'full' ) {
792
+
793
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_site_icon()', '4.0.0' );
794
+
795
+ $size = is_string( $size ) ? $size : 'full';
796
+
797
+ return \The_SEO_Framework\Builders\Images::get_site_icon_image_details( null, $size )->current()['url'];
798
  }
799
 
800
  /**
801
+ * Fetches site logo brought in WordPress 4.5
802
  *
803
+ * @since 2.8.0
804
+ * @since 3.0.0 Now sets preferred canonical URL scheme.
805
+ * @since 3.1.2 Now returns empty when it's deemed too small, and OG images are set.
806
+ * @since 4.0.0 Deprecated.
807
+ * @deprecated
 
808
  *
809
+ * @return string URL site logo, not escaped.
 
 
 
810
  */
811
+ public function get_site_logo() {
812
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->get_site_logo()', '4.0.0' );
813
+ return \The_SEO_Framework\Builders\Images::get_site_logo_image_details()->current()['url'];
 
814
  }
815
 
816
  /**
817
+ * Sanitizeses ID. Mainly removing spaces and coding characters.
818
  *
819
+ * Unlike sanitize_key(), it doesn't alter the case nor applies filters.
820
+ * It also maintains the '@' character.
 
 
821
  *
822
+ * @see WordPress Core sanitize_key()
823
+ * @since 3.1.0
824
+ * @since 4.0.0 1. Now allows square brackets.
825
+ * 2. Deprecated.
826
+ * @deprecated
827
+ *
828
+ * @param string $id The unsanitized ID.
829
+ * @return string The sanitized ID.
830
  */
831
+ public function sanitize_field_id( $id ) {
832
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->sanitize_field_id()', '4.0.0', 'the_seo_framework()->s_field_id()' );
833
+ return preg_replace( '/[^a-zA-Z0-9\[\]_\-@]/', '', $id );
 
834
  }
835
 
836
  /**
837
+ * Checks a theme's support for title-tag.
838
  *
839
+ * @since 2.6.0
840
+ * @since 3.1.0 Removed caching
841
+ * @since 4.0.0 Deprecated.
842
+ * @deprecated
 
 
843
  *
844
+ * @return bool
 
 
 
 
 
 
 
 
845
  */
846
+ public function current_theme_supports_title_tag() {
847
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->sanitize_field_id()', '4.0.0' );
848
+ return \the_seo_framework()->detect_theme_support( 'title-tag' );
849
+ }
850
+
851
+ /**
852
+ * Determines if the current theme supports the custom logo addition.
853
+ *
854
+ * @since 2.8.0
855
+ * @since 3.1.0: 1. No longer checks for WP version 4.5+.
856
+ * 2. No longer uses caching.
857
+ * @since 4.0.0 Deprecated.
858
+ * @deprecated
859
+ *
860
+ * @return bool
861
+ */
862
+ public function can_use_logo() {
863
+ \the_seo_framework()->_deprecated_function( 'the_seo_framework()->can_use_logo()', '4.0.0' );
864
+ return \the_seo_framework()->detect_theme_support( 'custom-logo' );
865
  }
866
  }
inc/classes/detect.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -32,59 +34,12 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
32
  */
33
  class Detect extends Render {
34
 
35
- /**
36
- * Determines if we're doing ajax.
37
- *
38
- * @todo use wp_doing_ajax() in a future version. Requires WP 4.7+.
39
- * @since 2.9.0
40
- * @staticvar bool $cache
41
- *
42
- * @return bool True if AJAX
43
- */
44
- public function doing_ajax() {
45
- static $cache = null;
46
-
47
- return isset( $cache ) ? $cache : $cache = defined( 'DOING_AJAX' ) && DOING_AJAX;
48
- }
49
-
50
- /**
51
- * Tests if input URL matches current domain.
52
- *
53
- * @since 2.9.4
54
- *
55
- * @param string $url The URL to test. Required.
56
- * @return bool true on match, false otherwise.
57
- */
58
- public function matches_this_domain( $url ) {
59
-
60
- if ( ! $url )
61
- return false;
62
-
63
- static $home_domain;
64
-
65
- if ( ! $home_domain ) {
66
- $home_domain = \esc_url_raw( \get_home_url(), [ 'http', 'https' ] );
67
- //= Simply convert to HTTPS/HTTP based on is_ssl()
68
- $home_domain = $this->set_url_scheme( $home_domain );
69
- }
70
-
71
- $url = \esc_url_raw( $url, [ 'http', 'https' ] );
72
- //= Simply convert to HTTPS/HTTP based on is_ssl()
73
- $url = $this->set_url_scheme( $url );
74
-
75
- //= If they start with the same, we can assume it's the same domain.
76
- if ( 0 === stripos( $url, $home_domain ) )
77
- return true;
78
-
79
- return false;
80
- }
81
-
82
  /**
83
  * Returns list of active plugins.
84
  *
85
  * @since 2.6.1
86
  * @staticvar array $active_plugins
87
- * @credits JetPack for most code.
88
  *
89
  * @return array List of active plugins.
90
  */
@@ -115,7 +70,7 @@ class Detect extends Render {
115
  * Filterable list of conflicting plugins.
116
  *
117
  * @since 2.6.0
118
- * @credits JetPack for most code.
119
  *
120
  * @return array List of conflicting plugins.
121
  */
@@ -198,7 +153,6 @@ class Detect extends Render {
198
  foreach ( $plugins['globals'] as $name ) {
199
  if ( isset( $GLOBALS[ $name ] ) ) {
200
  return true;
201
- break;
202
  }
203
  }
204
  }
@@ -208,7 +162,6 @@ class Detect extends Render {
208
  foreach ( $plugins['constants'] as $name ) {
209
  if ( defined( $name ) ) {
210
  return true;
211
- break;
212
  }
213
  }
214
  }
@@ -218,7 +171,6 @@ class Detect extends Render {
218
  foreach ( $plugins['functions'] as $name ) {
219
  if ( function_exists( $name ) ) {
220
  return true;
221
- break;
222
  }
223
  }
224
  }
@@ -228,7 +180,6 @@ class Detect extends Render {
228
  foreach ( $plugins['classes'] as $name ) {
229
  if ( class_exists( $name ) ) {
230
  return true;
231
- break;
232
  }
233
  }
234
  }
@@ -245,9 +196,9 @@ class Detect extends Render {
245
  * @staticvar array $cache
246
  * @uses $this->detect_plugin_multi()
247
  *
248
- * @param array $plugins Array of array for globals, constants, classes
249
- * and/or functions to check for plugin existence.
250
- * @param bool $use_cache Bypasses cache if false
251
  */
252
  public function can_i_use( array $plugins = [], $use_cache = true ) {
253
 
@@ -264,6 +215,7 @@ class Detect extends Render {
264
  return false; // doing it wrong...
265
 
266
  //* Sort alphanumeric by value, put values back after sorting.
 
267
  $func = array_flip( $func );
268
  ksort( $func );
269
  $func = array_flip( $func );
@@ -273,7 +225,8 @@ class Detect extends Render {
273
  }
274
 
275
  ksort( $mapped );
276
- $key = serialize( $mapped ); // phpcs:ignore -- No objects are inserted, nor is this ever unserialized.
 
277
 
278
  if ( isset( $cache[ $key ] ) )
279
  return $cache[ $key ];
@@ -297,7 +250,6 @@ class Detect extends Render {
297
  foreach ( $plugins['classes'] as $name ) {
298
  if ( ! class_exists( $name ) ) {
299
  return false;
300
- break;
301
  }
302
  }
303
  }
@@ -307,7 +259,6 @@ class Detect extends Render {
307
  foreach ( $plugins['functions'] as $name ) {
308
  if ( ! function_exists( $name ) ) {
309
  return false;
310
- break;
311
  }
312
  }
313
  }
@@ -317,7 +268,6 @@ class Detect extends Render {
317
  foreach ( $plugins['constants'] as $name ) {
318
  if ( ! defined( $name ) ) {
319
  return false;
320
- break;
321
  }
322
  }
323
  }
@@ -353,7 +303,6 @@ class Detect extends Render {
353
  $theme = strtolower( $theme );
354
  if ( $theme === $theme_parent || $theme === $theme_name ) {
355
  return true;
356
- break;
357
  }
358
  }
359
  }
@@ -424,8 +373,8 @@ class Detect extends Render {
424
  return $detected;
425
 
426
  //* Detect SEO plugins beforehand.
427
- if ( $detected = $this->detect_seo_plugins() )
428
- return $detected;
429
 
430
  $active_plugins = $this->active_plugins();
431
 
@@ -474,8 +423,8 @@ class Detect extends Render {
474
  return $detected;
475
 
476
  //* Detect SEO plugins beforehand.
477
- if ( $detected = $this->detect_seo_plugins() )
478
- return $detected;
479
 
480
  $active_plugins = $this->active_plugins();
481
 
@@ -540,8 +489,8 @@ class Detect extends Render {
540
  return $detected;
541
 
542
  //* Detect SEO plugins beforehand.
543
- if ( $detected = $this->detect_seo_plugins() )
544
- return $detected;
545
 
546
  $active_plugins = $this->active_plugins();
547
 
@@ -572,6 +521,36 @@ class Detect extends Render {
572
  return $detected = (bool) $detected;
573
  }
574
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
575
  /**
576
  * Determines whether to add a line within robots based by plugin detection, or sitemap output option.
577
  *
@@ -581,6 +560,8 @@ class Detect extends Render {
581
  * @since 2.9.2 Now also checks for permalinks.
582
  * @since 2.9.3 Now also checks for sitemap_robots option.
583
  * @since 3.1.0 Removed Jetpack's sitemap check -- it's no longer valid.
 
 
584
  *
585
  * @param bool $check_option Whether to check for sitemap option.
586
  * @return bool True when no conflicting plugins are detected or when The SEO Framework's Sitemaps are output.
@@ -588,23 +569,19 @@ class Detect extends Render {
588
  public function can_do_sitemap_robots( $check_option = true ) {
589
 
590
  if ( $check_option ) {
591
- if ( ! $this->get_option( 'sitemaps_output' ) || ! $this->get_option( 'sitemaps_robots' ) )
 
592
  return false;
593
  }
594
 
595
- if ( $this->is_subdirectory_installation() )
596
- return false;
597
-
598
- if ( ! $this->pretty_permalinks )
599
- return false;
600
-
601
- return true;
602
  }
603
 
604
  /**
605
  * Detects presence of robots.txt in root folder.
606
  *
607
  * @since 2.5.2
 
608
  * @staticvar $has_robots
609
  *
610
  * @return bool Whether the robots.txt file exists.
@@ -616,6 +593,9 @@ class Detect extends Render {
616
  if ( isset( $has_robots ) )
617
  return $has_robots;
618
 
 
 
 
619
  $path = \get_home_path() . 'robots.txt';
620
 
621
  return $has_robots = file_exists( $path );
@@ -625,6 +605,7 @@ class Detect extends Render {
625
  * Detects presence of sitemap.xml in root folder.
626
  *
627
  * @since 2.5.2
 
628
  * @staticvar bool $has_map
629
  *
630
  * @return bool Whether the sitemap.xml file exists.
@@ -636,6 +617,9 @@ class Detect extends Render {
636
  if ( isset( $has_map ) )
637
  return $has_map;
638
 
 
 
 
639
  $path = \get_home_path() . 'sitemap.xml';
640
 
641
  return $has_map = file_exists( $path );
@@ -685,7 +669,6 @@ class Detect extends Render {
685
  foreach ( (array) $features as $feature ) {
686
  if ( \current_theme_supports( $feature ) ) {
687
  return true;
688
- break;
689
  }
690
  continue;
691
  }
@@ -693,18 +676,6 @@ class Detect extends Render {
693
  return false;
694
  }
695
 
696
- /**
697
- * Checks a theme's support for title-tag.
698
- *
699
- * @since 2.6.0
700
- * @since 3.1.0 Removed caching
701
- *
702
- * @return bool
703
- */
704
- public function current_theme_supports_title_tag() {
705
- return $this->detect_theme_support( 'title-tag' );
706
- }
707
-
708
  /**
709
  * Detect if the current screen type is a page or taxonomy.
710
  *
@@ -726,7 +697,6 @@ class Detect extends Render {
726
  foreach ( $post_page as $screen ) {
727
  if ( $type === $screen ) {
728
  return $is_page[ $type ] = true;
729
- break;
730
  }
731
  }
732
 
@@ -734,99 +704,75 @@ class Detect extends Render {
734
  }
735
 
736
  /**
737
- * Detect WordPress language.
738
- * Considers en_UK, en_US, en, etc.
739
  *
740
- * @since 2.6.0
741
- * @since 3.1.0 Removed caching.
742
  *
743
- * @param string $locale Required, the locale.
744
- * @return bool Whether the input $locale is in the current WordPress locale.
745
  */
746
- public function check_wp_locale( $locale = '' ) {
747
- return false !== strpos( \get_locale(), $locale );
748
- }
749
 
750
- /**
751
- * Determines if the post type is disabled from SEO all optimization.
752
- *
753
- * @since 3.1.0
754
- * @since 3.1.2 Now is fiterable.
755
- *
756
- * @param string $post_type The post type, optional. Leave empty to autodetermine type.
757
- * @return bool True if disabled, false otherwise.
758
- */
759
- public function is_post_type_disabled( $post_type = '' ) {
760
 
761
- $post_type = $post_type ?: \get_post_type() ?: $this->get_admin_post_type();
762
 
763
- /**
764
- * @since 3.1.2
765
- * @param bool $disabled
766
- * @param string $post_type
767
- */
768
- return \apply_filters( 'the_seo_framework_post_type_disabled',
769
- isset(
770
- $this->get_option( 'disabled_post_types' )[ $post_type ]
771
- ),
772
- $post_type
773
- );
774
- }
775
-
776
- /**
777
- * Determines if the post type is compatible with The SEO Framework inpost metabox.
778
- *
779
- * @since 2.3.5
780
- * @since 3.1.0 1. The first parameter is now required.
781
- * 2. Added caching.
782
- * @staticvar bool $has_filter
783
- *
784
- * @param string $post_type
785
- * @return bool True if post type is supported.
786
- */
787
- public function post_type_supports_inpost( $post_type ) {
788
-
789
- if ( ! $post_type ) return false;
790
 
791
- static $has_filter = null;
 
 
792
 
793
- if ( is_null( $has_filter ) )
794
- $has_filter = \has_filter( 'the_seo_framework_custom_post_type_support' );
 
795
 
796
- if ( $has_filter ) {
797
- /**
798
- * Determines the required post type features before TSF supports it.
799
- * @since 2.3.5
800
- * @since 3.0.4 Default parameter now is `[]` instead of `['title','editor']`.
801
- * @param array $supports The required post type support, like 'title', 'editor'.
802
- */
803
- $supports = (array) \apply_filters( 'the_seo_framework_custom_post_type_support', [] );
804
 
805
- foreach ( $supports as $support ) {
806
- if ( ! \post_type_supports( $post_type, $support ) ) {
807
- return false;
808
- break;
809
- }
810
- continue;
811
- }
812
- }
813
 
814
- return true;
 
 
 
 
815
  }
816
 
817
  /**
818
- * Determines if post type supports The SEO Framework.
819
  *
820
- * @since 2.3.9
821
- * @since 3.1.0 1. Removed caching.
822
- * 2. Now works in admin.
823
  *
824
- * @param string $post_type The current post type.
825
- * @return bool true of post type is supported.
826
  */
827
- public function post_type_supports_custom_seo( $post_type = '' ) {
 
828
  $post_type = $post_type ?: \get_post_type() ?: $this->get_admin_post_type();
829
- return $post_type && $this->is_post_type_supported( $post_type ) && $this->post_type_supports_inpost( $post_type );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
830
  }
831
 
832
  /**
@@ -835,22 +781,23 @@ class Detect extends Render {
835
  * Checks if at least one taxonomy objects post type supports The SEO Framework,
836
  * and wether the taxonomy is public and rewritable.
837
  *
838
- * @since 3.1.0
839
  *
840
- * @param string $taxonomy The taxonomy name.
841
  * @return bool True if at least one post type in taxonomy isn't disabled.
842
  */
843
- public function taxonomy_supports_custom_seo( $taxonomy = '' ) {
844
 
845
  $taxonomy = $taxonomy ?: $this->get_current_taxonomy();
846
- if ( ! $taxonomy ) return false;
847
 
848
  /**
849
  * @since 3.1.0
 
850
  * @param bool $post_type Whether the post type is supported
851
  * @param string $post_type_evaluated The evaluated post type.
852
  */
853
- return (bool) \apply_filters_ref_array( 'the_seo_framework_supported_taxonomy',
 
854
  [
855
  $taxonomy
856
  && ! $this->is_taxonomy_disabled( $taxonomy )
@@ -860,32 +807,6 @@ class Detect extends Render {
860
  );
861
  }
862
 
863
- /**
864
- * Detects if the current or inputted post type is supported and not disabled.
865
- *
866
- * @since 3.1.0
867
- *
868
- * @param bool $post_type
869
- * @return bool
870
- */
871
- public function is_post_type_supported( $post_type = '' ) {
872
- $post_type = $post_type ?: \get_post_type() ?: $this->get_admin_post_type();
873
- /**
874
- * @since 2.6.2
875
- * @since 3.1.0 The first parameter is always a boolean now.
876
- * @param bool $supported Whether the post type is supported.
877
- * @param string $post_type_evaluated The evaluated post type.
878
- */
879
- return (bool) \apply_filters_ref_array( 'the_seo_framework_supported_post_type',
880
- [
881
- $post_type
882
- && ! $this->is_post_type_disabled( $post_type )
883
- && in_array( $post_type, $this->get_rewritable_post_types(), true ),
884
- $post_type,
885
- ]
886
- );
887
- }
888
-
889
  /**
890
  * Checks (current) Post Type for having taxonomical archives.
891
  *
@@ -941,11 +862,13 @@ class Detect extends Render {
941
  * @return array The post types with rewrite capabilities.
942
  */
943
  protected function get_rewritable_post_types() {
 
944
  static $cache = null;
945
- //? array_values() because get_post_types() gives a sequential array.
946
  return isset( $cache ) ? $cache : $cache = array_unique(
947
  array_merge(
948
  $this->get_forced_supported_post_types(),
 
949
  array_values( (array) \get_post_types( [
950
  'public' => true,
951
  'rewrite' => true,
@@ -978,27 +901,48 @@ class Detect extends Render {
978
  );
979
  }
980
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
981
  /**
982
  * Checks if at least one taxonomy objects post type supports The SEO Framework.
983
  *
984
  * @since 3.1.0
 
 
985
  *
986
  * @param string $taxonomy The taxonomy name.
987
  * @return bool True if at least one post type in taxonomy is supported.
988
  */
989
  public function is_taxonomy_disabled( $taxonomy = '' ) {
990
 
991
- $taxonomy = $taxonomy ?: $this->get_current_taxonomy();
992
- if ( ! $taxonomy ) return true;
993
-
994
- $tax = \get_taxonomy( $taxonomy );
995
-
996
- if ( false === $tax ) return true;
997
-
998
- if ( ! empty( $tax->object_type ) ) {
999
- foreach ( $tax->object_type as $type ) {
1000
- if ( ! $this->is_post_type_disabled( $type ) )
1001
- return false;
1002
  }
1003
  }
1004
 
@@ -1058,16 +1002,42 @@ class Detect extends Render {
1058
  }
1059
 
1060
  /**
1061
- * Determines if the current theme supports the custom logo addition.
1062
  *
1063
- * @since 2.8.0
1064
- * @since 3.1.0: 1. No longer checks for WP version 4.5+.
1065
- * 2. No longer uses caching.
1066
  *
1067
  * @return bool
1068
  */
1069
- public function can_use_logo() {
1070
- return $this->detect_theme_support( 'custom-logo' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1071
  }
1072
 
1073
  /**
@@ -1085,7 +1055,7 @@ class Detect extends Render {
1085
  if ( isset( $cache ) )
1086
  return $cache;
1087
 
1088
- $parsed_url = \wp_parse_url( \get_option( 'home' ) );
1089
 
1090
  return $cache = ! empty( $parsed_url['path'] ) && ltrim( $parsed_url['path'], ' \\/' );
1091
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Detect
4
+ * @subpackage The_SEO_Framework\Compatibility
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
34
  */
35
  class Detect extends Render {
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  /**
38
  * Returns list of active plugins.
39
  *
40
  * @since 2.6.1
41
  * @staticvar array $active_plugins
42
+ * @credits Jetpack for most code.
43
  *
44
  * @return array List of active plugins.
45
  */
70
  * Filterable list of conflicting plugins.
71
  *
72
  * @since 2.6.0
73
+ * @credits Jetpack for most code.
74
  *
75
  * @return array List of conflicting plugins.
76
  */
153
  foreach ( $plugins['globals'] as $name ) {
154
  if ( isset( $GLOBALS[ $name ] ) ) {
155
  return true;
 
156
  }
157
  }
158
  }
162
  foreach ( $plugins['constants'] as $name ) {
163
  if ( defined( $name ) ) {
164
  return true;
 
165
  }
166
  }
167
  }
171
  foreach ( $plugins['functions'] as $name ) {
172
  if ( function_exists( $name ) ) {
173
  return true;
 
174
  }
175
  }
176
  }
180
  foreach ( $plugins['classes'] as $name ) {
181
  if ( class_exists( $name ) ) {
182
  return true;
 
183
  }
184
  }
185
  }
196
  * @staticvar array $cache
197
  * @uses $this->detect_plugin_multi()
198
  *
199
+ * @param array $plugins Array of array for globals, constants, classes
200
+ * and/or functions to check for plugin existence.
201
+ * @param bool $use_cache Bypasses cache if false
202
  */
203
  public function can_i_use( array $plugins = [], $use_cache = true ) {
204
 
215
  return false; // doing it wrong...
216
 
217
  //* Sort alphanumeric by value, put values back after sorting.
218
+ // TODO Use asort or usort instead???
219
  $func = array_flip( $func );
220
  ksort( $func );
221
  $func = array_flip( $func );
225
  }
226
 
227
  ksort( $mapped );
228
+ // phpcs:ignore, WordPress.PHP.DiscouragedPHPFunctions -- No objects are inserted, nor is this ever unserialized.
229
+ $key = serialize( $mapped );
230
 
231
  if ( isset( $cache[ $key ] ) )
232
  return $cache[ $key ];
250
  foreach ( $plugins['classes'] as $name ) {
251
  if ( ! class_exists( $name ) ) {
252
  return false;
 
253
  }
254
  }
255
  }
259
  foreach ( $plugins['functions'] as $name ) {
260
  if ( ! function_exists( $name ) ) {
261
  return false;
 
262
  }
263
  }
264
  }
268
  foreach ( $plugins['constants'] as $name ) {
269
  if ( ! defined( $name ) ) {
270
  return false;
 
271
  }
272
  }
273
  }
303
  $theme = strtolower( $theme );
304
  if ( $theme === $theme_parent || $theme === $theme_name ) {
305
  return true;
 
306
  }
307
  }
308
  }
373
  return $detected;
374
 
375
  //* Detect SEO plugins beforehand.
376
+ if ( $this->detect_seo_plugins() )
377
+ return $detected = true;
378
 
379
  $active_plugins = $this->active_plugins();
380
 
423
  return $detected;
424
 
425
  //* Detect SEO plugins beforehand.
426
+ if ( $this->detect_seo_plugins() )
427
+ return $detected = true;
428
 
429
  $active_plugins = $this->active_plugins();
430
 
489
  return $detected;
490
 
491
  //* Detect SEO plugins beforehand.
492
+ if ( $this->detect_seo_plugins() )
493
+ return $detected = true;
494
 
495
  $active_plugins = $this->active_plugins();
496
 
521
  return $detected = (bool) $detected;
522
  }
523
 
524
+ /**
525
+ * Detects presence of a page builder.
526
+ *
527
+ * Detects the following builders:
528
+ * - Elementor by Elementor LTD
529
+ * - Divi Builder by Elegant Themes
530
+ * - Visual Composer by WPBakery
531
+ * - Page Builder by SiteOrigin
532
+ * - Beaver Builder by Fastline Media
533
+ *
534
+ * @since 4.0.0
535
+ * @staticvar bool $detected
536
+ *
537
+ * @return bool
538
+ */
539
+ public function detect_page_builder() {
540
+
541
+ static $detected = null;
542
+
543
+ return isset( $detected ) ? $detected : $detected = $this->detect_plugin( [
544
+ 'constants' => [
545
+ 'ELEMENTOR_VERSION',
546
+ 'ET_BUILDER_VERSION',
547
+ 'WPB_VC_VERSION',
548
+ 'SITEORIGIN_PANELS_VERSION',
549
+ 'FL_BUILDER_VERSION',
550
+ ],
551
+ ] );
552
+ }
553
+
554
  /**
555
  * Determines whether to add a line within robots based by plugin detection, or sitemap output option.
556
  *
560
  * @since 2.9.2 Now also checks for permalinks.
561
  * @since 2.9.3 Now also checks for sitemap_robots option.
562
  * @since 3.1.0 Removed Jetpack's sitemap check -- it's no longer valid.
563
+ * @since 4.0.0 : 1. Now uses has_robots_txt()
564
+ * : 2. Now uses the get_robots_txt_url() to determine validity.
565
  *
566
  * @param bool $check_option Whether to check for sitemap option.
567
  * @return bool True when no conflicting plugins are detected or when The SEO Framework's Sitemaps are output.
569
  public function can_do_sitemap_robots( $check_option = true ) {
570
 
571
  if ( $check_option ) {
572
+ if ( ! $this->get_option( 'sitemaps_output' )
573
+ || ! $this->get_option( 'sitemaps_robots' ) )
574
  return false;
575
  }
576
 
577
+ return ! $this->has_robots_txt() && strlen( $this->get_robots_txt_url() );
 
 
 
 
 
 
578
  }
579
 
580
  /**
581
  * Detects presence of robots.txt in root folder.
582
  *
583
  * @since 2.5.2
584
+ * @since 4.0.0 Now tries to load `wp-admin/includes/file.php` to prevent a fatal error.
585
  * @staticvar $has_robots
586
  *
587
  * @return bool Whether the robots.txt file exists.
593
  if ( isset( $has_robots ) )
594
  return $has_robots;
595
 
596
+ // Ensure get_home_path() is declared.
597
+ require_once ABSPATH . 'wp-admin/includes/file.php';
598
+
599
  $path = \get_home_path() . 'robots.txt';
600
 
601
  return $has_robots = file_exists( $path );
605
  * Detects presence of sitemap.xml in root folder.
606
  *
607
  * @since 2.5.2
608
+ * @since 4.0.0 Now tries to load `wp-admin/includes/file.php` to prevent a fatal error.
609
  * @staticvar bool $has_map
610
  *
611
  * @return bool Whether the sitemap.xml file exists.
617
  if ( isset( $has_map ) )
618
  return $has_map;
619
 
620
+ // Ensure get_home_path() is declared.
621
+ require_once ABSPATH . 'wp-admin/includes/file.php';
622
+
623
  $path = \get_home_path() . 'sitemap.xml';
624
 
625
  return $has_map = file_exists( $path );
669
  foreach ( (array) $features as $feature ) {
670
  if ( \current_theme_supports( $feature ) ) {
671
  return true;
 
672
  }
673
  continue;
674
  }
676
  return false;
677
  }
678
 
 
 
 
 
 
 
 
 
 
 
 
 
679
  /**
680
  * Detect if the current screen type is a page or taxonomy.
681
  *
697
  foreach ( $post_page as $screen ) {
698
  if ( $type === $screen ) {
699
  return $is_page[ $type ] = true;
 
700
  }
701
  }
702
 
704
  }
705
 
706
  /**
707
+ * Determines whether the main query supports custom SEO.
 
708
  *
709
+ * @since 4.0.0
710
+ * @since 4.0.2 Now tests for an existing post/term ID when on singular/term pages.
711
  *
712
+ * @return bool
 
713
  */
714
+ public function query_supports_seo() {
 
 
715
 
716
+ static $cache;
 
 
 
 
 
 
 
 
 
717
 
718
+ if ( isset( $cache ) ) return $cache;
719
 
720
+ switch ( true ) :
721
+ case $this->is_feed():
722
+ $supported = false;
723
+ break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
724
 
725
+ case $this->is_singular():
726
+ $supported = $this->is_post_type_supported() && $this->get_the_real_ID();
727
+ break;
728
 
729
+ case \is_post_type_archive():
730
+ $supported = $this->is_post_type_supported();
731
+ break;
732
 
733
+ case $this->is_term_meta_capable():
734
+ $supported = $this->is_post_type_supported() && $this->get_the_real_ID();
735
+ break;
 
 
 
 
 
736
 
737
+ default:
738
+ $supported = true;
739
+ break;
740
+ endswitch;
 
 
 
 
741
 
742
+ /**
743
+ * @since 4.0.0
744
+ * @param bool $supported Whether the query supports SEO.
745
+ */
746
+ return $cache = (bool) \apply_filters( 'the_seo_framework_query_supports_seo', $supported );
747
  }
748
 
749
  /**
750
+ * Detects if the current or inputted post type is supported and not disabled.
751
  *
752
+ * @since 3.1.0
 
 
753
  *
754
+ * @param string $post_type Optional. The post type to check.
755
+ * @return bool
756
  */
757
+ public function is_post_type_supported( $post_type = '' ) {
758
+
759
  $post_type = $post_type ?: \get_post_type() ?: $this->get_admin_post_type();
760
+
761
+ /**
762
+ * @since 2.6.2
763
+ * @since 3.1.0 The first parameter is always a boolean now.
764
+ * @param bool $supported Whether the post type is supported.
765
+ * @param string $post_type_evaluated The evaluated post type.
766
+ */
767
+ return (bool) \apply_filters_ref_array(
768
+ 'the_seo_framework_supported_post_type',
769
+ [
770
+ $post_type
771
+ && ! $this->is_post_type_disabled( $post_type )
772
+ && in_array( $post_type, $this->get_rewritable_post_types(), true ),
773
+ $post_type,
774
+ ]
775
+ );
776
  }
777
 
778
  /**
781
  * Checks if at least one taxonomy objects post type supports The SEO Framework,
782
  * and wether the taxonomy is public and rewritable.
783
  *
784
+ * @since 4.0.0
785
  *
786
+ * @param string $taxonomy Optional. The taxonomy name.
787
  * @return bool True if at least one post type in taxonomy isn't disabled.
788
  */
789
+ public function is_taxonomy_supported( $taxonomy = '' ) {
790
 
791
  $taxonomy = $taxonomy ?: $this->get_current_taxonomy();
 
792
 
793
  /**
794
  * @since 3.1.0
795
+ * @since 4.0.0 Now returns only returns false when all post types in the taxonomy aren't supported.
796
  * @param bool $post_type Whether the post type is supported
797
  * @param string $post_type_evaluated The evaluated post type.
798
  */
799
+ return (bool) \apply_filters_ref_array(
800
+ 'the_seo_framework_supported_taxonomy',
801
  [
802
  $taxonomy
803
  && ! $this->is_taxonomy_disabled( $taxonomy )
807
  );
808
  }
809
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
810
  /**
811
  * Checks (current) Post Type for having taxonomical archives.
812
  *
862
  * @return array The post types with rewrite capabilities.
863
  */
864
  protected function get_rewritable_post_types() {
865
+
866
  static $cache = null;
867
+
868
  return isset( $cache ) ? $cache : $cache = array_unique(
869
  array_merge(
870
  $this->get_forced_supported_post_types(),
871
+ //? array_values() because get_post_types() gives a sequential array.
872
  array_values( (array) \get_post_types( [
873
  'public' => true,
874
  'rewrite' => true,
901
  );
902
  }
903
 
904
+
905
+ /**
906
+ * Determines if the post type is disabled from SEO all optimization.
907
+ *
908
+ * @since 3.1.0
909
+ * @since 3.1.2 Now is fiterable.
910
+ *
911
+ * @param string $post_type Optional. The post type to check.
912
+ * @return bool True if disabled, false otherwise.
913
+ */
914
+ public function is_post_type_disabled( $post_type = '' ) {
915
+
916
+ $post_type = $post_type ?: \get_post_type() ?: $this->get_admin_post_type();
917
+
918
+ /**
919
+ * @since 3.1.2
920
+ * @param bool $disabled
921
+ * @param string $post_type
922
+ */
923
+ return \apply_filters( 'the_seo_framework_post_type_disabled',
924
+ isset(
925
+ $this->get_option( 'disabled_post_types' )[ $post_type ]
926
+ ),
927
+ $post_type
928
+ );
929
+ }
930
+
931
  /**
932
  * Checks if at least one taxonomy objects post type supports The SEO Framework.
933
  *
934
  * @since 3.1.0
935
+ * @since 4.0.0 1. Now returns true if at least one post type for the taxonomy is supported.
936
+ * 2. Now uses `is_post_type_supported()` instead of `is_post_type_disabled()`.
937
  *
938
  * @param string $taxonomy The taxonomy name.
939
  * @return bool True if at least one post type in taxonomy is supported.
940
  */
941
  public function is_taxonomy_disabled( $taxonomy = '' ) {
942
 
943
+ foreach ( $this->get_post_types_from_taxonomy( $taxonomy ) as $type ) {
944
+ if ( $this->is_post_type_supported( $type ) ) {
945
+ return false;
 
 
 
 
 
 
 
 
946
  }
947
  }
948
 
1002
  }
1003
 
1004
  /**
1005
+ * Determines whether we can output sitemap or not based on options and blog status.
1006
  *
1007
+ * @since 2.6.0
1008
+ * @since 2.9.2 No longer checks for plain and ugly permalinks.
1009
+ * @since 4.0.0 Removed caching.
1010
  *
1011
  * @return bool
1012
  */
1013
+ public function can_run_sitemap() {
1014
+ return $this->get_option( 'sitemaps_output' ) && ! $this->current_blog_is_spam_or_deleted();
1015
+ }
1016
+
1017
+ /**
1018
+ * Returns the robots.txt location URL.
1019
+ * Only allows root domains.
1020
+ *
1021
+ * @since 2.9.2
1022
+ * @since 4.0.2 Now uses the preferred URL scheme.
1023
+ * @global \WP_Rewrite $wp_rewrite
1024
+ *
1025
+ * @return string URL location of robots.txt. Unescaped.
1026
+ */
1027
+ public function get_robots_txt_url() {
1028
+ global $wp_rewrite;
1029
+
1030
+ if ( $wp_rewrite->using_permalinks() && ! $this->is_subdirectory_installation() ) {
1031
+ $home = \trailingslashit( $this->set_preferred_url_scheme( $this->get_home_host() ) );
1032
+ $path = "{$home}robots.txt";
1033
+ } elseif ( $this->has_robots_txt() ) {
1034
+ $home = \trailingslashit( $this->set_preferred_url_scheme( \get_option( 'home' ) ) );
1035
+ $path = "{$home}robots.txt";
1036
+ } else {
1037
+ $path = '';
1038
+ }
1039
+
1040
+ return $path;
1041
  }
1042
 
1043
  /**
1055
  if ( isset( $cache ) )
1056
  return $cache;
1057
 
1058
+ $parsed_url = parse_url( \get_option( 'home' ) );
1059
 
1060
  return $cache = ! empty( $parsed_url['path'] ) && ltrim( $parsed_url['path'], ' \\/' );
1061
  }
inc/classes/doing-it-right.class.php DELETED
@@ -1,1814 +0,0 @@
1
- <?php
2
- /**
3
- * @package The_SEO_Framework\Classes
4
- */
5
- namespace The_SEO_Framework;
6
-
7
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
8
-
9
- /**
10
- * The SEO Framework plugin
11
- * Copyright (C) 2015 - 2019 Sybre Waaijer, CyberWire (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
- /**
27
- * Class The_SEO_Framework\Doing_It_Right
28
- *
29
- * Adds data in a column to edit.php and edit-tags.php
30
- * Shows you if you're doing the SEO right.
31
- *
32
- * @since 2.8.0
33
- * @TODO make abstract and extend for post and term types.
34
- * See `TSFEP\Output\Output()` (private plugin)
35
- */
36
- class Doing_It_Right extends Generate_Ldjson {
37
-
38
- /**
39
- * Add post state on edit.php to the page or post that has been altered.
40
- *
41
- * @since 2.1.0
42
- * @uses $this->add_post_state
43
- */
44
- public function post_state() {
45
-
46
- //* Only load on singular pages.
47
- if ( $this->is_singular() ) {
48
- /**
49
- * @since 2.1.0
50
- * @param bool $allow_states Whether to allow TSF post states output.
51
- */
52
- $allow_states = (bool) \apply_filters( 'the_seo_framework_allow_states', true );
53
-
54
- if ( $allow_states )
55
- \add_filter( 'display_post_states', [ $this, 'add_post_state' ], 10, 2 );
56
- }
57
- }
58
-
59
- /**
60
- * Adds post states in post/page edit.php query
61
- *
62
- * @since 2.1.0
63
- * @since 2.9.4 Now listens to `alter_search_query` and `alter_archive_query` options.
64
- *
65
- * @param array $states The current post states array
66
- * @param \WP_Post $post The Post Object.
67
- * @return array Adjusted $states
68
- */
69
- public function add_post_state( $states = [], $post ) {
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_custom_field( 'exclude_local_search', $post_id );
75
- $archive_exclude = $this->get_option( 'alter_archive_query' ) && $this->get_custom_field( 'exclude_from_archive', $post_id );
76
-
77
- if ( $search_exclude )
78
- $states[] = \esc_html__( 'No Search', 'autodescription' );
79
-
80
- if ( $archive_exclude )
81
- $states[] = \esc_html__( 'No Archive', 'autodescription' );
82
- }
83
-
84
- return $states;
85
- }
86
-
87
- /**
88
- * Initializes SEO columns for adding a tag or category.
89
- *
90
- * @since 2.9.1
91
- * @securitycheck 3.0.0 OK.
92
- * @access private
93
- */
94
- public function _init_columns_wp_ajax_add_tag() {
95
-
96
- /**
97
- * Securely check the referrer, instead of leaving holes everywhere.
98
- */
99
- if ( $this->doing_ajax() && \check_ajax_referer( 'add-tag', '_wpnonce_add-tag', false ) ) {
100
-
101
- // TODO why is 'post_tag' here? FIXME?
102
- $taxonomy = ! empty( $_POST['taxonomy'] ) ? \sanitize_key( $_POST['taxonomy'] ) : 'post_tag';
103
-
104
- if ( ! $this->taxonomy_supports_custom_seo( $taxonomy ) )
105
- return;
106
-
107
- if ( \current_user_can( \get_taxonomy( $taxonomy )->cap->edit_terms ) )
108
- $this->init_columns( '', true );
109
- }
110
- }
111
-
112
- /**
113
- * Initializes SEO columns for adding a tag or category.
114
- *
115
- * @since 2.9.1
116
- * @securitycheck 3.0.0 OK.
117
- * @access private
118
- */
119
- public function _init_columns_wp_ajax_inline_save() {
120
-
121
- /**
122
- * Securely check the referrer, instead of leaving holes everywhere.
123
- */
124
- if ( $this->doing_ajax() && \check_ajax_referer( 'inlineeditnonce', '_inline_edit', false ) ) {
125
- $post_type = isset( $_POST['post_type'] ) ? \sanitize_key( $_POST['post_type'] ) : false;
126
-
127
- if ( $post_type && isset( $_POST['post_ID'] ) ) {
128
- $post_id = (int) $_POST['post_ID'];
129
- $access = false;
130
-
131
- $pto = \get_post_type_object( $post_type );
132
- if ( isset( $pto->capability_type ) )
133
- $access = \current_user_can( 'edit_' . $pto->capability_type, $post_id );
134
-
135
- if ( $access )
136
- $this->init_columns( '', true );
137
- }
138
- }
139
- }
140
-
141
- /**
142
- * Initializes SEO columns for adding a tag or category.
143
- *
144
- * @since 2.9.1
145
- * @securitycheck 3.0.0 OK.
146
- * @access private
147
- */
148
- public function _init_columns_wp_ajax_inline_save_tax() {
149
-
150
- /**
151
- * Securely check the referrer, instead of leaving holes everywhere.
152
- */
153
- if ( $this->doing_ajax() && \check_ajax_referer( 'taxinlineeditnonce', '_inline_edit', false ) ) {
154
- if ( empty( $_POST['taxonomy'] ) ) return;
155
- if ( empty( $_POST['tax_ID'] ) ) return;
156
-
157
- $taxonomy = \sanitize_key( $_POST['taxonomy'] );
158
-
159
- if ( ! $this->taxonomy_supports_custom_seo( $taxonomy ) )
160
- return;
161
-
162
- $tax_id = (int) $_POST['tax_ID'];
163
-
164
- if ( \current_user_can( 'edit_term', $tax_id ) )
165
- $this->init_columns( '', true );
166
- }
167
- }
168
-
169
- /**
170
- * Initializes SEO bar columns.
171
- *
172
- * @since 2.1.9
173
- * @since 2.9.1 Now supports inline edit AJAX.
174
- * @securitycheck 3.0.0 OK. NOTE: Sanity check is done in _init_columns_wp_ajax_inline_save_tax()
175
- * & _init_columns_wp_ajax_inline_save()
176
- * @TODO clean me up.
177
- *
178
- * @param \WP_Screen|string $screen \WP_Screen
179
- * @param bool $doing_ajax Whether we're doing an AJAX response.
180
- * @return void If filter is set to false.
181
- */
182
- public function init_columns( $screen = '', $doing_ajax = false ) {
183
-
184
- /**
185
- * @since 2.9.1 or earlier.
186
- * @param bool $show_seo_column Whether to show the SEO Bar column.
187
- */
188
- $show_seo_column = (bool) \apply_filters( 'the_seo_framework_show_seo_column', true );
189
-
190
- if ( false === $show_seo_column )
191
- return;
192
-
193
- if ( $doing_ajax ) {
194
- /** For CSRF @see $this->_init_columns_wp_ajax_inline_save_tax(). */
195
- $post_type = isset( $_POST['post_type'] ) ? \sanitize_key( $_POST['post_type'] ) : ''; // CSRF ok
196
- $post_type = $post_type ?: ( isset( $_POST['tax_type'] ) ? \sanitize_key( $_POST['tax_type'] ) : '' ); // CSRF ok
197
- } else {
198
- $post_type = isset( $screen->post_type ) ? $screen->post_type : '';
199
- }
200
-
201
- if ( $this->post_type_supports_custom_seo( $post_type ) ) :
202
- if ( $doing_ajax ) {
203
- /** For CSRF @see $this->init_columns_ajax(). */
204
- $id = isset( $_POST['screen'] ) ? \sanitize_key( $_POST['screen'] ) : false; // CSRF ok
205
- $taxonomy = isset( $_POST['taxonomy'] ) ? \sanitize_key( $_POST['taxonomy'] ) : false; // CSRF ok
206
-
207
- if ( $id ) {
208
- //* Everything but inline-save-tax action.
209
- \add_filter( 'manage_' . $id . '_columns', [ $this, 'add_column' ], 10, 1 );
210
-
211
- /**
212
- * Always load pages and posts.
213
- * Many CPT plugins rely on these.
214
- */
215
- \add_action( 'manage_posts_custom_column', [ $this, 'seo_bar_ajax' ], 1, 3 );
216
- \add_action( 'manage_pages_custom_column', [ $this, 'seo_bar_ajax' ], 1, 3 );
217
- } elseif ( $taxonomy ) {
218
-
219
- if ( ! $this->taxonomy_supports_custom_seo( $taxonomy ) )
220
- return;
221
-
222
- //* Action: inline-save-tax does not POST screen.
223
- \add_filter( 'manage_edit-' . $taxonomy . '_columns', [ $this, 'add_column' ], 1, 1 );
224
- }
225
-
226
- if ( $taxonomy && $this->taxonomy_supports_custom_seo( $taxonomy ) )
227
- \add_filter( 'manage_' . $taxonomy . '_custom_column', [ $this, 'get_taxonomy_seo_bar_ajax' ], 1, 3 );
228
-
229
- } else {
230
- $id = isset( $screen->id ) ? $screen->id : '';
231
-
232
- if ( '' !== $id && $this->is_wp_lists_edit() ) {
233
-
234
- $taxonomy = isset( $screen->taxonomy ) ? $screen->taxonomy : '';
235
-
236
- if ( $taxonomy ) {
237
- if ( ! $this->taxonomy_supports_custom_seo( $taxonomy ) )
238
- return;
239
-
240
- \add_filter( 'manage_' . $taxonomy . '_custom_column', [ $this, 'get_taxonomy_seo_bar' ], 1, 3 );
241
- }
242
-
243
- \add_filter( 'manage_' . $id . '_columns', [ $this, 'add_column' ], 10, 1 );
244
- /**
245
- * Always load pages and posts.
246
- * Many CPT plugins rely on these.
247
- */
248
- \add_action( 'manage_posts_custom_column', [ $this, 'seo_bar' ], 1, 3 );
249
- \add_action( 'manage_pages_custom_column', [ $this, 'seo_bar' ], 1, 3 );
250
- }
251
- }
252
- endif;
253
- }
254
-
255
- /**
256
- * Adds SEO column on edit(-tags).php
257
- *
258
- * Also determines where the column should be placed. Preferred before comments, then data, then tags.
259
- * If neither found, it will add the column to the end.
260
- *
261
- * @since 2.1.9
262
- *
263
- * @param array $columns The existing columns
264
- * @return array $columns the column data
265
- */
266
- public function add_column( $columns ) {
267
-
268
- $seocolumn = [ 'tsf-seo-bar-wrap' => 'SEO' ];
269
-
270
- $column_keys = array_keys( $columns );
271
-
272
- //* Column keys to look for, in order of appearance.
273
- $order_keys = [
274
- 'comments',
275
- 'posts',
276
- 'date',
277
- 'tags',
278
- ];
279
-
280
- /**
281
- * @since 2.8.0
282
- * @param array $order_keys The keys where the SEO column may be prepended to.
283
- * The first key found will be used.
284
- */
285
- $order_keys = (array) \apply_filters( 'the_seo_framework_seo_column_keys_order', $order_keys );
286
-
287
- foreach ( $order_keys as $key ) {
288
- //* Put value in $offset, if not false, break loop.
289
- if ( false !== ( $offset = array_search( $key, $column_keys, true ) ) )
290
- break;
291
- }
292
-
293
- //* I tried but found nothing
294
- if ( false === $offset ) {
295
- //* Add SEO bar at the end of columns.
296
- $columns = array_merge( $columns, $seocolumn );
297
- } else {
298
- //* Add seo bar between columns.
299
-
300
- //* Cache columns.
301
- $columns_before = $columns;
302
-
303
- $columns = array_merge(
304
- array_splice( $columns, 0, $offset ),
305
- $seocolumn,
306
- array_splice( $columns_before, $offset )
307
- );
308
- }
309
-
310
- return $columns;
311
- }
312
-
313
- /**
314
- * Echos the SEO Bar.
315
- *
316
- * @since 2.6.0
317
- *
318
- * @param string $column the current column : If it's a taxonomy, this is empty
319
- * @param int $post_id the post id : If it's a taxonomy, this is the column name
320
- * @param string $tax_id this is empty : If it's a taxonomy, this is the taxonomy id
321
- */
322
- public function seo_bar( $column, $post_id, $tax_id = '' ) {
323
- echo $this->get_seo_bar( $column, $post_id, $tax_id );
324
- }
325
-
326
- /**
327
- * Returns the SEO Bar for taxonomies.
328
- *
329
- * @since 3.0.4
330
- *
331
- * @param string $string The current column string.
332
- * @param string $column_name Name of the column.
333
- * @param string $term_id Term ID.
334
- */
335
- public function get_taxonomy_seo_bar( $string, $column_name, $term_id ) {
336
- return $string . $this->get_seo_bar( '', $column_name, $term_id );
337
- }
338
-
339
- /**
340
- * Returns the SEO Bar.
341
- *
342
- * @since 3.0.4
343
- * @staticvar string $type
344
- *
345
- * @param string $column the current column : If it's a taxonomy, this is empty
346
- * @param int $post_id the post id : If it's a taxonomy, this is the column name
347
- * @param string $tax_id this is empty : If it's a taxonomy, this is the taxonomy id
348
- */
349
- public function get_seo_bar( $column, $post_id, $tax_id = '' ) {
350
-
351
- static $type = null;
352
-
353
- if ( ! isset( $type ) ) {
354
- $type = \get_post_type( $post_id );
355
-
356
- if ( false === $type || '' !== $tax_id ) {
357
- $type = $this->get_current_taxonomy();
358
- }
359
- }
360
-
361
- /**
362
- * Params are shifted.
363
- * @link https://core.trac.wordpress.org/ticket/33521
364
- */
365
- if ( '' !== $tax_id ) {
366
- $column = $post_id;
367
- $post_id = $tax_id;
368
- }
369
-
370
- if ( 'tsf-seo-bar-wrap' === $column )
371
- return $this->post_status( $post_id, $type );
372
-
373
- return '';
374
- }
375
-
376
- /**
377
- * Echos the SEO column in edit screens on AJAX.
378
- *
379
- * @since 2.1.9
380
- *
381
- * @param string $column the current column : If it's a taxonomy, this is empty
382
- * @param int $post_id the post id : If it's a taxonomy, this is the column name
383
- * @param string $tax_id this is empty : If it's a taxonomy, this is the taxonomy id
384
- */
385
- public function seo_bar_ajax( $column, $post_id, $tax_id = '' ) {
386
- echo $this->get_seo_bar_ajax( $column, $post_id, $tax_id );
387
- }
388
-
389
- /**
390
- * Returns the SEO Bar for taxonomies on AJAX.
391
- *
392
- * @since 3.0.4
393
- *
394
- * @param string $string The current column string.
395
- * @param string $column_name Name of the column.
396
- * @param string $term_id Term ID.
397
- */
398
- public function get_taxonomy_seo_bar_ajax( $string, $column_name, $term_id ) {
399
- return $string . $this->get_seo_bar_ajax( '', $column_name, $term_id );
400
- }
401
-
402
- /**
403
- * Returns the SEO column in edit screens on AJAX.
404
- *
405
- * @since 3.0.4
406
- *
407
- * @param string $column the current column : If it's a taxonomy, this is empty
408
- * @param int $post_id the post id : If it's a taxonomy, this is the column name
409
- * @param string $tax_id this is empty : If it's a taxonomy, this is the taxonomy id
410
- */
411
- public function get_seo_bar_ajax( $column, $post_id, $tax_id = '' ) {
412
-
413
- $is_term = false;
414
-
415
- /**
416
- * Params are shifted.
417
- * @link https://core.trac.wordpress.org/ticket/33521
418
- */
419
- if ( '' !== $tax_id ) {
420
- $is_term = true;
421
- $column = $post_id;
422
- $post_id = $tax_id;
423
- }
424
-
425
- if ( 'tsf-seo-bar-wrap' === $column ) {
426
- $context = \esc_html__( 'Refresh to see the SEO Bar status.', 'autodescription' );
427
-
428
- $ajax_id = $column . $post_id;
429
-
430
- return $this->post_status_special( $context, '?', 'unknown', $is_term, $ajax_id );
431
- }
432
-
433
- return '';
434
- }
435
-
436
- /**
437
- * Wrap a single-line block for the SEO bar, showing special statuses.
438
- *
439
- * @since 2.6.0
440
- *
441
- * @param string $context The hover/screenreader context.
442
- * @param string $symbol The single-character symbol.
443
- * @param string $class The SEO block color code. : 'bad', 'okay', 'good', 'unknown'.
444
- * @param int|null $ajax_id The unique Ajax ID to generate a small on-hover script for this ID. May be Arbitrary.
445
- * @param bool $echo Whether to echo the output.
446
- * @return string|void The special block with wrap. Void if $echo is true.
447
- */
448
- protected function post_status_special( $context, $symbol = '?', $color = 'unknown', $is_term = '', $ajax_id = null, $echo = false ) {
449
-
450
- $classes = $this->get_the_seo_bar_classes();
451
-
452
- $block = $this->wrap_the_seo_bar_block( [
453
- 'class' => $classes[ $color ],
454
- 'width' => '',
455
- 'notice' => $context,
456
- 'indicator' => $symbol,
457
- ] );
458
-
459
- if ( empty( $is_term ) )
460
- $is_term = $this->is_archive();
461
-
462
- $bar = $this->get_the_seo_bar_wrap( $block, $is_term, $ajax_id );
463
-
464
- if ( $echo ) {
465
- echo $bar; // xss ok, already escaped.
466
- return;
467
- } else {
468
- return $bar;
469
- }
470
- }
471
-
472
- /**
473
- * Renders post status. Caches the output.
474
- *
475
- * @since 2.1.9
476
- * @staticvar string $post_i18n The post type slug.
477
- * @staticvar bool $is_term If we're dealing with TT pages.
478
- * @since 2.8.0 Third parameter `$echo` has been put into effect.
479
- *
480
- * @param int $post_id The Post ID or taxonomy ID.
481
- * @param string $type The content type.
482
- * @param bool $echo Whether to echo the value. Does not eliminate return.
483
- * @return string|void $content The post SEO status. Void if $echo is true.
484
- */
485
- public function post_status( $post_id = '', $type = 'inpost', $echo = false ) {
486
-
487
- $content = '';
488
-
489
- //* Fetch Post ID if it hasn't been provided.
490
- if ( ! $post_id )
491
- $post_id = $this->get_the_real_ID();
492
-
493
- if ( $post_id ) {
494
- //* Fetch Post Type.
495
- if ( 'inpost' === $type || ! $type ) {
496
- $type = \get_post_type( $post_id );
497
- }
498
-
499
- //* No need to re-evalute these.
500
- static $post_i18n, $is_term, $taxonomy, $post_type;
501
-
502
- $term = null;
503
- /**
504
- * Static caching.
505
- * @since 2.3.8
506
- */
507
- if ( ! isset( $post_i18n, $is_term, $taxonomy, $post_type ) ) {
508
- if ( $this->is_post_type_page( $type ) ) {
509
- $is_term = false;
510
- $post_i18n = $this->get_post_type_label( $type );
511
- $post_type = $type;
512
- } else {
513
- $is_term = true;
514
- $term = $this->fetch_the_term( $post_id );
515
- $taxonomy = $this->get_current_taxonomy();
516
- $post_type = $this->get_admin_post_type();
517
- $post_i18n =
518
- ( isset( $term->taxonomy ) ? $this->get_tax_type_label( $term->taxonomy ) : '' )
519
- ?: $this->get_post_type_label( $type );
520
- }
521
- }
522
-
523
- $post_low = $this->maybe_lowercase_noun( $post_i18n );
524
-
525
- $args = [
526
- 'is_term' => $is_term,
527
- 'post_id' => $post_id,
528
- 'post_i18n' => $post_i18n,
529
- 'post_low' => $post_low,
530
- 'post_type' => $post_type,
531
- 'taxonomy' => $taxonomy,
532
- ];
533
-
534
- if ( $is_term ) {
535
- $bar = $this->the_seo_bar_term( $args );
536
- } else {
537
- $bar = $this->the_seo_bar_page( $args );
538
- }
539
- } else {
540
- $context = \esc_attr__( 'Failed to fetch post ID.', 'autodescription' );
541
-
542
- $bar = $this->post_status_special( $context, '!', 'bad' );
543
- }
544
-
545
- if ( $echo ) {
546
- echo $bar; // xss ok, already escaped.
547
- return;
548
- } else {
549
- return $bar;
550
- }
551
- }
552
-
553
- /**
554
- * Returns a part of the SEO Bar based on parameters.
555
- *
556
- * @since 2.6.0
557
- * @since 3.0.0 Now uses spans instead of a's
558
- * @since 3.1.0 Now forges a tooltip wrap.
559
- *
560
- * @param array $args : {
561
- * string $indicator Required. The block text.
562
- * string $notice Required. The tooltip message.
563
- * string $width Required. The width class.
564
- * string $class Required. The item class.
565
- * }
566
- * @return string The SEO Bar block part.
567
- */
568
- protected function wrap_the_seo_bar_block( $args ) {
569
- return vsprintf(
570
- '<span class="tsf-seo-bar-section-wrap tsf-tooltip-wrap %s">%s</span>',
571
- [
572
- $args['width'],
573
- vsprintf(
574
- '<span class="tsf-seo-bar-item tsf-tooltip-item %s" data-desc="%s" aria-label="%s">%s</span>',
575
- [
576
- $args['class'],
577
- $args['notice'],
578
- $this->strip_tags_cs( $args['notice'], [ 'clear' => 'br' ] ),
579
- $args['indicator'],
580
- ]
581
- ),
582
- ]
583
- );
584
- }
585
-
586
- /**
587
- * Wrap the SEO bar.
588
- *
589
- * If Ajax ID is set, a small jQuery script will also be output to reset the
590
- * DOM element for the status bar hover.
591
- *
592
- * @since 2.6.0
593
- * @since 3.1.0 1. No longer maintains cache.
594
- * 2. No longer can create pill-shaped bars.
595
- * 3. No longer outputs a tooltip wrap. This has been shifted a layer downwards.
596
- *
597
- * @param string $content The SEO Bar content.
598
- * @param bool $is_term Whether the bar is for a term.
599
- * @param int|null $ajax_id The unique Ajax ID to generate a small on-hover script for.
600
- * @return string The SEO Bar wrapped.
601
- */
602
- protected function get_the_seo_bar_wrap( $content, $is_term, $ajax_id = null ) {
603
-
604
- if ( isset( $ajax_id ) ) {
605
- //= Ajax handler.
606
-
607
- //* Resets tooltips.
608
- $script = '<script>tsfTT.triggerReset();</script>';
609
-
610
- return sprintf(
611
- '<span class="tsf-seo-bar clearfix" id="%s"><span class="tsf-seo-bar-inner-wrap">%s</span></span>',
612
- \esc_attr( $ajax_id ),
613
- $content
614
- ) . $script;
615
- }
616
-
617
- return sprintf(
618
- '<span class="tsf-seo-bar clearfix"><span class="tsf-seo-bar-inner-wrap">%s</span></span>',
619
- $content
620
- );
621
- }
622
-
623
- /**
624
- * Output the SEO bar for Terms and Taxonomies.
625
- *
626
- * @since 2.6.0
627
- *
628
- * @param array $args {
629
- * 'is_term' => bool $is_term,
630
- * 'post_i18n' => string $post_i18n,
631
- * 'post_low' => string $post_low,
632
- * 'type' => string $type,
633
- * }
634
- * @return string $content The SEO bar.
635
- */
636
- protected function the_seo_bar_term( $args ) {
637
-
638
- $post_i18n = $args['post_i18n'];
639
- $is_term = true;
640
-
641
- $data = $this->get_term_meta( $args['post_id'] );
642
-
643
- $noindex = ! empty( $data['noindex'] );
644
- $redirect = false; // We don't apply redirect on taxonomies (yet)
645
-
646
- //* Blocked SEO, return simple bar.
647
- if ( $redirect || $noindex )
648
- return $this->the_seo_bar_blocked( compact( 'is_term', 'redirect', 'noindex', 'post_i18n' ) );
649
-
650
- $title_notice = $this->the_seo_bar_title_notice( $args );
651
- $description_notice = $this->the_seo_bar_description_notice( $args );
652
- $index_notice = $this->the_seo_bar_index_notice( $args );
653
- $follow_notice = $this->the_seo_bar_follow_notice( $args );
654
- $archive_notice = $this->the_seo_bar_archive_notice( $args );
655
-
656
- $content = $title_notice . $description_notice . $index_notice . $follow_notice . $archive_notice;
657
-
658
- return $this->get_the_seo_bar_wrap( $content, $is_term );
659
- }
660
-
661
- /**
662
- * Output the SEO bar for Terms and Taxonomies.
663
- *
664
- * @since 2.6.0
665
- *
666
- * @param array $args {
667
- * 'is_term' => $is_term,
668
- * 'post_id' => $post_id,
669
- * 'post_i18n' => $post_i18n,
670
- * 'post_low' => $post_low,
671
- * 'type' => $type,
672
- * }
673
- * @return string $content The SEO bar.
674
- */
675
- protected function the_seo_bar_page( $args ) {
676
-
677
- $post_i18n = $args['post_i18n'];
678
-
679
- $is_term = false;
680
- $is_front_page = $this->is_static_frontpage( $args['post_id'] );
681
-
682
- $redirect = (bool) $this->get_custom_field( 'redirect', $args['post_id'] );
683
- $noindex = (bool) $this->get_custom_field( '_genesis_noindex', $args['post_id'] );
684
-
685
- if ( $is_front_page )
686
- $noindex = $this->get_option( 'homepage_noindex' ) ?: $noindex;
687
-
688
- if ( $redirect || $noindex )
689
- return $this->the_seo_bar_blocked( compact( 'is_term', 'redirect', 'noindex', 'post_i18n' ) );
690
-
691
- $title_notice = $this->the_seo_bar_title_notice( $args );
692
- $description_notice = $this->the_seo_bar_description_notice( $args );
693
- $index_notice = $this->the_seo_bar_index_notice( $args );
694
- $follow_notice = $this->the_seo_bar_follow_notice( $args );
695
- $archive_notice = $this->the_seo_bar_archive_notice( $args );
696
- $redirect_notice = $this->the_seo_bar_redirect_notice( $args );
697
-
698
- $content = $title_notice . $description_notice . $index_notice . $follow_notice . $archive_notice . $redirect_notice;
699
-
700
- return $this->get_the_seo_bar_wrap( $content, $is_term );
701
- }
702
-
703
- /**
704
- * Fetch the post or term data for The SEO Bar, structured and cached.
705
- *
706
- * @since 2.6.0
707
- * @staticvar array $data
708
- *
709
- * @param array $args The term/post args.
710
- * @return array $data {
711
- * 'title' => $title,
712
- * 'title_is_from_custom_field' => $title_is_from_custom_field,
713
- * 'description' => $description,
714
- * 'description_is_from_custom_field' => $description_is_from_custom_field,
715
- * 'nofollow' => $nofollow,
716
- * 'noarchive' => $noarchive
717
- * }
718
- */
719
- protected function the_seo_bar_data( $args ) {
720
-
721
- static $data = [];
722
-
723
- if ( isset( $data[ $args['post_id'] ] ) )
724
- return $data[ $args['post_id'] ];
725
-
726
- return $data[ $args['post_id'] ] = $args['is_term']
727
- ? $this->the_seo_bar_term_data( $args )
728
- : $this->the_seo_bar_post_data( $args );
729
- }
730
-
731
- /**
732
- * Fetch the term data for The SEO Bar.
733
- *
734
- * @since 2.6.0
735
- * @since 2.9.0 Now also returns noindex value.
736
- * @since 3.0.6 Now considers custom field filters for the description.
737
- * @staticvar array $data
738
- *
739
- * @param array $args The term args.
740
- * @return array $data {
741
- * $title,
742
- * $title_is_from_custom_field,
743
- * $description,
744
- * $description_is_from_custom_field,
745
- * $noindex,
746
- * $nofollow,
747
- * $noarchive
748
- * }
749
- */
750
- protected function the_seo_bar_term_data( $args ) {
751
-
752
- $data = $this->get_term_meta( $args['post_id'] );
753
-
754
- $title_custom_field = isset( $data['doctitle'] ) ? $data['doctitle'] : '';
755
-
756
- $description_custom_field = $this->get_description_from_custom_field( [
757
- 'id' => $args['post_id'],
758
- 'taxonomy' => $args['taxonomy'],
759
- ] );
760
-
761
- $noindex = isset( $data['noindex'] ) ? $data['noindex'] : '';
762
- $nofollow = isset( $data['nofollow'] ) ? $data['nofollow'] : '';
763
- $noarchive = isset( $data['noarchive'] ) ? $data['noarchive'] : '';
764
-
765
- $title_is_from_custom_field = (bool) $title_custom_field;
766
-
767
- $title = $this->get_title( [
768
- 'id' => $args['post_id'],
769
- 'taxonomy' => $args['taxonomy'],
770
- ] );
771
-
772
- $description_is_from_custom_field = (bool) $description_custom_field;
773
- //= Call sanitized version.
774
- if ( $description_is_from_custom_field ) {
775
- $description = $description_custom_field;
776
- } else {
777
- $description = $this->get_generated_description( [
778
- 'id' => $args['post_id'],
779
- 'taxonomy' => $args['taxonomy'],
780
- ] );
781
- }
782
-
783
- $noindex = (bool) $noindex;
784
- $nofollow = (bool) $nofollow;
785
- $noarchive = (bool) $noarchive;
786
-
787
- return compact(
788
- 'title',
789
- 'title_is_from_custom_field',
790
- 'description',
791
- 'description_is_from_custom_field',
792
- 'noindex',
793
- 'nofollow',
794
- 'noarchive'
795
- );
796
- }
797
-
798
- /**
799
- * Fetch the post data for The SEO Bar.
800
- *
801
- * @since 2.6.0
802
- * @since 2.9.0 Now also returns noindex value.
803
- * @since 3.0.6 Now considers custom field filters for the description.
804
- * @staticvar array $data
805
- *
806
- * @param array $args The post args.
807
- * @return array $data {
808
- * $title,
809
- * $title_is_from_custom_field,
810
- * $description,
811
- * $description_is_from_custom_field,
812
- * $noindex,
813
- * $nofollow,
814
- * $noarchive
815
- * }
816
- */
817
- protected function the_seo_bar_post_data( $args ) {
818
-
819
- $title_custom_field = $this->get_custom_field( '_genesis_title', $args['post_id'] );
820
- $description_custom_field = $this->get_description_from_custom_field( [ 'id' => $args['post_id'] ] );
821
- $noindex = $this->get_custom_field( '_genesis_noindex', $args['post_id'] );
822
- $nofollow = $this->get_custom_field( '_genesis_nofollow', $args['post_id'] );
823
- $noarchive = $this->get_custom_field( '_genesis_noarchive', $args['post_id'] );
824
-
825
- if ( $this->is_static_frontpage( $args['post_id'] ) ) {
826
- $title_custom_field = $this->get_option( 'homepage_title' ) ?: $title_custom_field;
827
- // $description_custom_field = $description_custom_field; // We already got this.
828
- $noindex = $this->get_option( 'homepage_noindex' ) ?: $nofollow;
829
- $nofollow = $this->get_option( 'homepage_nofollow' ) ?: $nofollow;
830
- $noarchive = $this->get_option( 'homepage_noarchive' ) ?: $noarchive;
831
- }
832
-
833
- $title = $this->get_title( [ 'id' => $args['post_id'] ] );
834
-
835
- $title_is_from_custom_field = (bool) $title_custom_field;
836
-
837
- $description_is_from_custom_field = (bool) $description_custom_field;
838
- //= Call sanitized version.
839
- if ( $description_is_from_custom_field ) {
840
- $description = $description_custom_field;
841
- } else {
842
- $description = $this->get_generated_description( $args['post_id'] );
843
- }
844
-
845
- $noindex = (bool) $noindex;
846
- $nofollow = (bool) $nofollow;
847
- $noarchive = (bool) $noarchive;
848
-
849
- return compact(
850
- 'title',
851
- 'title_is_from_custom_field',
852
- 'description',
853
- 'description_is_from_custom_field',
854
- 'noindex',
855
- 'nofollow',
856
- 'noarchive'
857
- );
858
- }
859
-
860
- /**
861
- * Render the SEO bar title block and notice.
862
- *
863
- * @since 2.6.0
864
- * @since 3.1.0 No longer converts quotes for length calculation.
865
- *
866
- * @param array $args
867
- * @return string The SEO Bar Title Block
868
- */
869
- protected function the_seo_bar_title_notice( $args ) {
870
-
871
- //* Fetch data
872
- $data = $this->the_seo_bar_data( $args );
873
- $title = $data['title'];
874
-
875
- $title_is_from_custom_field = $data['title_is_from_custom_field'];
876
-
877
- //* Fetch CSS classes.
878
- $classes = $this->get_the_seo_bar_classes();
879
-
880
- //* Fetch i18n and put in vars
881
- $i18n = $this->get_the_seo_bar_i18n();
882
- $title_short = $i18n['title_short'];
883
- $generated = $i18n['generated_short'];
884
- $and_i18n = $i18n['and'];
885
- $but_i18n = $i18n['but'];
886
-
887
- //* Initialize notice.
888
- $notice = $i18n['title'];
889
- $class = $classes['good'];
890
-
891
- //* Generated notice.
892
- $generated_notice = '<br>' . $i18n['generated'];
893
- $gen_t = $title_is_from_custom_field ? '' : $generated;
894
- $gen_t_notice = $title_is_from_custom_field ? '' : $generated_notice;
895
-
896
- //* Title length. Convert &#8230; to a single character as well.
897
- $tit_len = (int) mb_strlen(
898
- html_entity_decode(
899
- \wp_specialchars_decode( $title, ENT_QUOTES ),
900
- ENT_NOQUOTES // Quotes no longer need conversion.
901
- )
902
- );
903
-
904
- //* Length notice.
905
- $title_length_warning = $this->get_the_seo_bar_title_length_warning( $tit_len, $class );
906
- $notice .= $title_length_warning ? ' ' . $title_length_warning['notice'] : '';
907
- $class = $title_length_warning['class'];
908
-
909
- $title_duplicated = false;
910
- //* Check if title is duplicated from blogname.
911
- if ( $this->use_title_branding() ) {
912
- //* We are using blognames in titles.
913
-
914
- $blogname = $this->get_blogname();
915
-
916
- $first = stripos( $title, $blogname );
917
- $last = strripos( $title, $blogname );
918
-
919
- if ( $first !== $last )
920
- $title_duplicated = true;
921
- }
922
-
923
- if ( $title_duplicated ) {
924
- //* If the title is good, we should use And. Otherwise 'But'.
925
- $but_and = $title_length_warning['but'] ? $but_i18n : $and_i18n;
926
-
927
- /* translators: %s = But or And */
928
- $notice .= '<br>' . sprintf( \esc_attr__( '%s the Title contains the Blogname multiple times.', 'autodescription' ), $but_and );
929
- $class = $classes['bad'];
930
- }
931
-
932
- //* Put everything together.
933
- $notice = $notice . $gen_t_notice;
934
- $title_short = $title_short . $gen_t;
935
-
936
- $tit_wrap_args = [
937
- 'indicator' => $title_short,
938
- 'notice' => $notice,
939
- 'width' => $classes['15%'],
940
- 'class' => $class,
941
- ];
942
-
943
- $title_notice = $this->wrap_the_seo_bar_block( $tit_wrap_args );
944
-
945
- return $title_notice;
946
- }
947
-
948
- /**
949
- * Render the SEO bar description block and notice.
950
- *
951
- * @since 2.6.0
952
- * @since 3.1.0 1. Fixed length calculation by no longer converting quotes.
953
- * 2. No longer outputs a "generated" notice when generation
954
- * is disabled via the new `auto_description` option.
955
- *
956
- * @param array $args
957
- * @return string The SEO Bar Description Block
958
- */
959
- protected function the_seo_bar_description_notice( $args ) {
960
-
961
- //* Fetch data
962
- $data = $this->the_seo_bar_data( $args );
963
- $description = $data['description'];
964
- $description_is_from_custom_field = $data['description_is_from_custom_field'];
965
-
966
- //* Fetch i18n and put in vars
967
- $i18n = $this->get_the_seo_bar_i18n();
968
- $description_short = $i18n['description_short'];
969
- $generated_short = $i18n['generated_short'];
970
-
971
- //* Description length. Convert &#8230; to a single character as well.
972
- $desc_len = (int) mb_strlen(
973
- html_entity_decode(
974
- \wp_specialchars_decode( $description, ENT_QUOTES ),
975
- ENT_NOQUOTES // Quotes no longer need conversion.
976
- )
977
- );
978
-
979
- //* Fetch CSS classes.
980
- $classes = $this->get_the_seo_bar_classes();
981
-
982
- //* Initialize notice.
983
- $notice = $i18n['description'];
984
- $class = $classes['good'];
985
-
986
- //* Length notice.
987
- $desc_length_warning = $this->get_the_seo_bar_description_length_warning( $desc_len, $class );
988
- $notice .= $desc_length_warning['notice'] ? ' ' . $desc_length_warning['notice'] . '<br>' : '';
989
- $class = $desc_length_warning['class'];
990
-
991
- //* Duplicated Words notice.
992
- $desc_too_many = $this->get_the_seo_bar_description_words_warning( $description, $class );
993
- $notice .= $desc_too_many['notice'] ? $desc_too_many['notice'] . '<br>' : '';
994
- $class = $desc_too_many['class'];
995
-
996
- //* Generation notice.
997
- if ( $this->get_option( 'auto_description' ) ) {
998
- $generated_notice = $i18n['generated'] . ' ';
999
-
1000
- $gen_d = $description_is_from_custom_field ? '' : $generated_short;
1001
- $gen_d_notice = $description_is_from_custom_field ? '' : $generated_notice;
1002
- } else {
1003
- $gen_d = $gen_d_notice = '';
1004
- }
1005
-
1006
- //* Put everything together.
1007
- $notice = $notice . $gen_d_notice;
1008
- $description_short = $description_short . $gen_d;
1009
-
1010
- $desc_wrap_args = [
1011
- 'indicator' => $description_short,
1012
- 'notice' => $notice,
1013
- 'width' => $classes['15%'],
1014
- 'class' => $class,
1015
- ];
1016
-
1017
- $description_notice = $this->wrap_the_seo_bar_block( $desc_wrap_args );
1018
-
1019
- return $description_notice;
1020
- }
1021
-
1022
- /**
1023
- * Description Length notices.
1024
- *
1025
- * @since 2.6.0
1026
- * @since 3.0.4 : 1. Threshold "too long" has been increased from 155 to 300.
1027
- * 2. Threshold "far too long" has been increased to 330 from 175.
1028
- * @since 3.1.0 Now uses the new guidelines via a filterable function.
1029
- *
1030
- * @param int $desc_len The Title length
1031
- * @param string $class The current color class.
1032
- * @return array {
1033
- * notice => The notice,
1034
- * class => The class,
1035
- * }
1036
- */
1037
- protected function get_the_seo_bar_description_length_warning( $desc_len, $class ) {
1038
-
1039
- $classes = $this->get_the_seo_bar_classes();
1040
- $i18n = $this->get_the_seo_bar_i18n();
1041
- $guidelines = $this->get_input_guidelines()['description']['search']['chars'];
1042
-
1043
- if ( ! $desc_len ) {
1044
- $notice = $i18n['length_empty'];
1045
- $class = $classes['unknown'];
1046
- } elseif ( $desc_len < $guidelines['lower'] ) {
1047
- $notice = $i18n['length_far_too_short'];
1048
- $class = $classes['bad'];
1049
- } elseif ( $desc_len < $guidelines['goodLower'] ) {
1050
- $notice = $i18n['length_too_short'];
1051
-
1052
- // Don't make it okay if it's already bad.
1053
- $class = $classes['bad'] === $class ? $class : $classes['okay'];
1054
- } elseif ( $desc_len > $guidelines['upper'] ) {
1055
- $notice = $i18n['length_far_too_long'];
1056
- $class = $classes['bad'];
1057
- } elseif ( $desc_len > $guidelines['goodUpper'] ) {
1058
- $notice = $i18n['length_too_long'];
1059
-
1060
- // Don't make it okay if it's already bad.
1061
- $class = $classes['bad'] === $class ? $class : $classes['okay'];
1062
- } else {
1063
- $notice = $i18n['length_good'];
1064
-
1065
- // Don't make it good if it's already bad or okay.
1066
- $class = $classes['good'] !== $class ? $class : $classes['good'];
1067
- }
1068
-
1069
- return compact( 'notice', 'class' );
1070
- }
1071
-
1072
- /**
1073
- * Calculates the word count and returns a warning with the words used.
1074
- * Only when count is over 3.
1075
- *
1076
- * @since 2.6.0
1077
- *
1078
- * @param string $description The Description with maybe words too many.
1079
- * @param string $class The current color class.
1080
- * @return string The warning notice.
1081
- */
1082
- protected function get_the_seo_bar_description_words_warning( $description, $class ) {
1083
-
1084
- $notice = '';
1085
-
1086
- $words_too_many = $this->get_word_count( $description );
1087
-
1088
- if ( ! empty( $words_too_many ) ) {
1089
-
1090
- $classes = $this->get_the_seo_bar_classes();
1091
- $bad = $classes['bad'];
1092
- $okay = $classes['okay'];
1093
-
1094
- $words_count = count( $words_too_many );
1095
- //* Don't make it okay if it's already bad.
1096
- $class = $bad !== $class && $words_count <= 1 ? $okay : $bad;
1097
-
1098
- $i = 1;
1099
- $count = count( $words_too_many );
1100
- foreach ( $words_too_many as $desc_array ) {
1101
- foreach ( $desc_array as $desc_value => $desc_count ) {
1102
- $notice .= ' ';
1103
-
1104
- /**
1105
- * Don't ucfirst abbreviations.
1106
- * @since 2.4.1
1107
- */
1108
- $desc_value = ctype_upper( $desc_value ) ? $desc_value : ucfirst( $desc_value );
1109
-
1110
- /* translators: 1: Word, 2: Occurrences */
1111
- $notice .= sprintf( \esc_attr__( '%1$s is used %2$d times.', 'autodescription' ), '<span>' . $desc_value . '</span>', $desc_count );
1112
-
1113
- //* Don't add break at last occurrence.
1114
- $notice .= $i === $count ? '' : '<br>';
1115
- $i++;
1116
- }
1117
- }
1118
- }
1119
-
1120
- return compact( 'notice', 'class' );
1121
- }
1122
-
1123
- /**
1124
- * Render the SEO bar index block and notice.
1125
- *
1126
- * @since 2.6.0
1127
- *
1128
- * @param array $args
1129
- * @return string The SEO Bar Index Block
1130
- */
1131
- protected function the_seo_bar_index_notice( $args ) {
1132
-
1133
- $data = $this->the_seo_bar_data( $args );
1134
-
1135
- $classes = $this->get_the_seo_bar_classes();
1136
- $unknown = $classes['unknown'];
1137
- $bad = $classes['bad'];
1138
- $okay = $classes['okay'];
1139
- $good = $classes['good'];
1140
- $ad_125 = $classes['12.5%'];
1141
-
1142
- $i18n = $this->get_the_seo_bar_i18n();
1143
- $index_short = $i18n['index_short'];
1144
- $but_i18n = $i18n['but'];
1145
- $and_i18n = $i18n['and'];
1146
- $ind_notice = $i18n['index'];
1147
-
1148
- /* Translators: %s = Post / Page / Category, etc. */
1149
- $ind_notice .= ' ' . sprintf( \esc_attr__( '%s is being indexed.', 'autodescription' ), $args['post_i18n'] );
1150
- $ind_class = $good;
1151
-
1152
- /**
1153
- * Get noindex site option
1154
- *
1155
- * @since 2.2.2
1156
- */
1157
- if ( $this->get_option( 'site_noindex' ) ) {
1158
- $ind_notice .= '<br>' . \esc_attr__( "But you've discouraged indexing for the whole site.", 'autodescription' );
1159
- $ind_class = $unknown;
1160
- $but = true;
1161
- }
1162
-
1163
- //* Adds notice for global archive indexing options.
1164
- if ( $args['is_term'] ) {
1165
- /**
1166
- * @staticvar bool $_checked
1167
- * @staticvar string $label
1168
- */
1169
- static $_checked, $_label;
1170
-
1171
- if ( ! isset( $_checked ) ) {
1172
- //* Fetch whether it's checked.
1173
- $_checked = $this->the_seo_bar_archive_robots_options( 'noindex' );
1174
- }
1175
-
1176
- if ( $_checked ) {
1177
- $but_and = isset( $but ) ? $and_i18n : $but_i18n;
1178
- $_label = $_label ?: $this->get_tax_type_label( $this->fetch_the_term( $args['post_id'] )->taxonomy, false );
1179
-
1180
- /* translators: 1: But or And, 2: Current taxonomy term plural label */
1181
- $ind_notice .= '<br>' . sprintf( \esc_attr__( '%1$s indexing for %2$s have been discouraged.', 'autodescription' ), $but_and, $_label );
1182
- $ind_class = $unknown;
1183
- $but = true;
1184
- }
1185
- } elseif ( $this->is_protected( $args['post_id'] ) ) {
1186
- // Posts only.
1187
-
1188
- $but_and = isset( $but ) ? $and_i18n : $but_i18n;
1189
- /* translators: 1 = But or And, 1 = Post/Page */
1190
- $ind_notice .= '<br>' . sprintf( \esc_attr__( '%1$s the %2$s is protected from public visibility. This means indexing is discouraged.', 'autodescription' ), $but_and, $args['post_i18n'] );
1191
- $ind_class = $unknown;
1192
- $but = true;
1193
- }
1194
-
1195
- //* Adds notice for robots post type settings.
1196
- static $pt_checked;
1197
- if ( ! isset( $pt_checked ) ) {
1198
- $_setting = $this->get_option( $this->get_robots_post_type_option_id( 'noindex' ) );
1199
- $pt_checked = ! empty( $_setting[ $args['post_type'] ] );
1200
- }
1201
- if ( $pt_checked ) {
1202
- $but_and = isset( $but ) ? $and_i18n : $but_i18n;
1203
- /* translators: %s = But or And */
1204
- $ind_notice .= '<br>' . sprintf( \esc_attr__( '%s the post type is discouraging from being indexed.', 'autodescription' ), $but_and );
1205
- $ind_class = $unknown;
1206
- $but = true;
1207
- }
1208
-
1209
- //* Adds notice for WordPress blog public indexing.
1210
- if ( false === $this->is_blog_public() ) {
1211
- $but_and = isset( $but ) ? $and_i18n : $but_i18n;
1212
- /* translators: %s = But or And */
1213
- $ind_notice .= '<br>' . sprintf( \esc_attr__( "%s the blog isn't set to public. This means WordPress discourages indexing.", 'autodescription' ), $but_and );
1214
- $ind_class = $bad;
1215
- $but = true;
1216
- }
1217
-
1218
- /**
1219
- * Check if archive is empty, and therefore has set noindex for those.
1220
- *
1221
- * @since 2.2.8
1222
- */
1223
- if ( $args['is_term'] ) {
1224
- $term = $this->fetch_the_term( $args['post_id'] );
1225
-
1226
- $_has_posts = ! empty( $term->count );
1227
-
1228
- if ( ! $_has_posts && isset( $term->term_id, $term->taxonomy ) ) {
1229
- $children = \get_term_children( $term->term_id, $term->taxonomy );
1230
- foreach ( $children as $child_id ) {
1231
- if ( $_child_term = \get_term( $child_id, $term->taxonomy ) ) {
1232
- $_has_posts = ! empty( $_child_term->count );
1233
- if ( $_has_posts ) break;
1234
- }
1235
- }
1236
- }
1237
-
1238
- if ( ! $_has_posts ) {
1239
- $but_and = isset( $but ) ? $and_i18n : $but_i18n;
1240
-
1241
- /* translators: %s = But or And */
1242
- $ind_notice .= '<br>' . sprintf( \esc_attr__( '%s there are no posts in this term; therefore, indexing has been discouraged.', 'autodescription' ), $but_and );
1243
- //* Don't make it unknown if it's not good.
1244
- $ind_class = $ind_class !== $good ? $ind_class : $unknown;
1245
- $but = true;
1246
- }
1247
- } else {
1248
- // Posts/pages only.
1249
-
1250
- if ( $this->is_blog_page( $args['post_id'] ) ) {
1251
- $posts = \get_posts( [
1252
- 'posts_per_page' => 1,
1253
- 'post_type' => 'post',
1254
- 'orderby' => 'date',
1255
- 'order' => 'DESC',
1256
- 'post_status' => 'publish',
1257
- 'has_password' => false,
1258
- 'fields' => 'ids',
1259
- 'cache_results' => false,
1260
- 'suppress_filters' => false,
1261
- 'no_found_rows' => true,
1262
- ] );
1263
- if ( ! count( $posts ) ) {
1264
- $but_and = isset( $but ) ? $and_i18n : $but_i18n;
1265
-
1266
- /* translators: %s = But or And */
1267
- $ind_notice .= '<br>' . sprintf( \esc_attr__( '%s there are no posts on this blog; therefore, indexing has been discouraged.', 'autodescription' ), $but_and );
1268
- //* Don't make it unknown if it's not good.
1269
- $ind_class = $ind_class !== $good ? $ind_class : $unknown;
1270
- $but = true;
1271
- }
1272
- // FIXME is this needed for WooCommerce Shop, too???
1273
- }
1274
-
1275
- if ( $this->is_draft( $args['post_id'] ) ) {
1276
- $but_and = isset( $but ) ? $and_i18n : $but_i18n;
1277
-
1278
- /* translators: 1 = But or And, 1 = Post/Page */
1279
- $ind_notice .= '<br>' . sprintf( \esc_attr__( '%1$s this %2$s is in draft; therefore, indexing has been discouraged.', 'autodescription' ), $but_and, $args['post_i18n'] );
1280
- //* Don't make it unknown if it's not good.
1281
- $ind_class = $ind_class !== $good ? $ind_class : $unknown;
1282
- }
1283
- }
1284
-
1285
- $ind_wrap_args = [
1286
- 'indicator' => $index_short,
1287
- 'notice' => $ind_notice,
1288
- 'width' => $ad_125,
1289
- 'class' => $ind_class,
1290
- ];
1291
-
1292
- $index_notice = $this->wrap_the_seo_bar_block( $ind_wrap_args );
1293
-
1294
- return $index_notice;
1295
- }
1296
-
1297
- /**
1298
- * Checks whether global index/archive/follow options are checked for archives.
1299
- *
1300
- * @since 2.6.0
1301
- * @staticvar bool $cache
1302
- *
1303
- * @param string $type : 'noindex', 'nofollow', 'noarchive'
1304
- * @return bool
1305
- */
1306
- protected function the_seo_bar_archive_robots_options( $type ) {
1307
-
1308
- $taxonomy = false;
1309
-
1310
- if ( $this->is_category() )
1311
- $taxonomy = 'category';
1312
-
1313
- if ( $this->is_tag() )
1314
- $taxonomy = 'tag';
1315
-
1316
- if ( $taxonomy ) {
1317
- static $cache = [];
1318
-
1319
- if ( isset( $cache[ $type ] ) )
1320
- return $cache[ $type ];
1321
-
1322
- if ( $this->get_option( $taxonomy . '_' . $type ) )
1323
- return $cache[ $type ] = true;
1324
-
1325
- return $cache[ $type ] = false;
1326
- }
1327
-
1328
- return false;
1329
- }
1330
-
1331
- /**
1332
- * Render the SEO bar follow block and notice.
1333
- *
1334
- * @since 2.6.0
1335
- *
1336
- * @param array $args
1337
- * @return string The SEO Bar Follow Block
1338
- */
1339
- protected function the_seo_bar_follow_notice( $args ) {
1340
-
1341
- $followed = true;
1342
-
1343
- $data = $this->the_seo_bar_data( $args );
1344
- $nofollow = $data['nofollow'];
1345
-
1346
- $classes = $this->get_the_seo_bar_classes();
1347
- $unknown = $classes['unknown'];
1348
- $bad = $classes['bad'];
1349
- $okay = $classes['okay'];
1350
- $good = $classes['good'];
1351
- $ad_125 = $classes['12.5%'];
1352
-
1353
- $i18n = $this->get_the_seo_bar_i18n();
1354
- $follow_i18n = $i18n['follow'];
1355
- $but_i18n = $i18n['but'];
1356
- $and_i18n = $i18n['and'];
1357
- $follow_short = $i18n['follow_short'];
1358
-
1359
- if ( $nofollow ) {
1360
- $fol_notice = $follow_i18n . ' ' . sprintf( \esc_attr__( "%s links aren't being followed.", 'autodescription' ), $args['post_i18n'] );
1361
- $fol_class = $unknown;
1362
- $fol_but = true;
1363
-
1364
- $followed = false;
1365
- } else {
1366
- $fol_notice = $follow_i18n . ' ' . sprintf( \esc_attr__( '%s links are being followed.', 'autodescription' ), $args['post_i18n'] );
1367
- $fol_class = $good;
1368
- }
1369
-
1370
- /**
1371
- * Get nofolow site option
1372
- *
1373
- * @since 2.2.2
1374
- */
1375
- if ( $this->get_option( 'site_nofollow' ) ) {
1376
- $but_and = isset( $fol_but ) ? $and_i18n : $but_i18n;
1377
- /* translators: %s = But or And */
1378
- $fol_notice .= '<br>' . sprintf( \esc_attr__( "%s you've discouraged the following of links for the whole site.", 'autodescription' ), $but_and );
1379
- $fol_class = $unknown;
1380
- $fol_but = true;
1381
-
1382
- $followed = false;
1383
- }
1384
-
1385
- //* Adds notice for global archive indexing options.
1386
- if ( $args['is_term'] ) {
1387
-
1388
- /**
1389
- * @staticvar bool $checked
1390
- * @staticvar string $label
1391
- */
1392
- static $checked = null;
1393
-
1394
- if ( ! isset( $checked ) ) {
1395
- //* Fetch whether it's checked.
1396
- $checked = $this->the_seo_bar_archive_robots_options( 'nofollow' );
1397
- }
1398
-
1399
- if ( $checked ) {
1400
- $but_and = isset( $fol_but ) ? $and_i18n : $but_i18n;
1401
- $label = $this->get_tax_type_label( $this->fetch_the_term( $args['post_id'] )->taxonomy, false );
1402
-
1403
- /* translators: 1: But or And, 2: Current taxonomy term plural label */
1404
- $fol_notice .= '<br>' . sprintf( \esc_attr__( '%1$s following of links for %2$s have been discouraged.', 'autodescription' ), $but_and, $label );
1405
- $fol_class = $unknown;
1406
-
1407
- $followed = false;
1408
- }
1409
- }
1410
-
1411
- //* Adds notice for robots post type settings.
1412
- static $pt_checked;
1413
- if ( ! isset( $pt_checked ) ) {
1414
- $_setting = $this->get_option( $this->get_robots_post_type_option_id( 'nofollow' ) );
1415
- $pt_checked = ! empty( $_setting[ $args['post_type'] ] );
1416
- }
1417
- if ( $pt_checked ) {
1418
- $but_and = isset( $but ) ? $and_i18n : $but_i18n;
1419
- /* translators: %s = But or And */
1420
- $fol_notice .= '<br>' . sprintf( \esc_attr__( '%s this post type is discouraging from having its links followed.', 'autodescription' ), $but_and );
1421
- $fol_class = $unknown;
1422
- $but = true;
1423
- }
1424
-
1425
- if ( false === $this->is_blog_public() ) {
1426
- //* Make it "and" if following has not been discouraged otherwise.
1427
- $but_and = $followed || ! isset( $fol_but ) ? $and_i18n : $but_i18n;
1428
-
1429
- /* translators: %s = But or And */
1430
- $fol_notice .= '<br>' . sprintf( \esc_attr__( "%s the blog isn't set to public. This means WordPress allows the links to be followed regardless.", 'autodescription' ), $but_and );
1431
- $fol_class = $followed ? $fol_class : $okay;
1432
- $fol_but = true;
1433
-
1434
- $followed = false;
1435
- }
1436
-
1437
- $fol_wrap_args = [
1438
- 'indicator' => $follow_short,
1439
- 'notice' => $fol_notice,
1440
- 'width' => $ad_125,
1441
- 'class' => $fol_class,
1442
- ];
1443
-
1444
- $follow_notice = $this->wrap_the_seo_bar_block( $fol_wrap_args );
1445
-
1446
- return $follow_notice;
1447
- }
1448
-
1449
- /**
1450
- * Render the SEO bar archive block and notice.
1451
- *
1452
- * @since 2.6.0
1453
- *
1454
- * @param array $args
1455
- * @return string The SEO Bar Follow Block
1456
- */
1457
- protected function the_seo_bar_archive_notice( $args ) {
1458
-
1459
- $archived = true;
1460
-
1461
- $data = $this->the_seo_bar_data( $args );
1462
-
1463
- $classes = $this->get_the_seo_bar_classes();
1464
- $unknown = $classes['unknown'];
1465
- $bad = $classes['bad'];
1466
- $okay = $classes['okay'];
1467
- $good = $classes['good'];
1468
- $ad_125 = $classes['12.5%'];
1469
-
1470
- $i18n = $this->get_the_seo_bar_i18n();
1471
- $archive_i18n = $i18n['archive'];
1472
- $but_i18n = $i18n['but'];
1473
- $and_i18n = $i18n['and'];
1474
- $archive_short = $i18n['archive_short'];
1475
-
1476
- if ( $data['noarchive'] ) {
1477
- /* translators: %s is Post/Page/Term */
1478
- $arc_notice = $archive_i18n . ' ' . sprintf( \esc_attr__( 'Bots are discouraged to archive this %s.', 'autodescription' ), $args['post_low'] );
1479
- $arc_class = $unknown;
1480
- $archived = false;
1481
- } else {
1482
- /* translators: %s is Post/Page/Term */
1483
- $arc_notice = $archive_i18n . ' ' . sprintf( \esc_attr__( 'Bots are allowed to archive this %s.', 'autodescription' ), $args['post_low'] );
1484
- $arc_class = $good;
1485
- $arc_but = true;
1486
- }
1487
-
1488
- /**
1489
- * Get noarchive site option
1490
- *
1491
- * @since 2.2.2
1492
- */
1493
- if ( $this->get_option( 'site_noarchive' ) ) {
1494
- $but_and = isset( $arc_but ) ? $and_i18n : $but_i18n;
1495
-
1496
- $arc_notice .= '<br>' . sprintf( \esc_attr__( "%s you've discouraged archiving for the whole site.", 'autodescription' ), $but_and );
1497
- $arc_class = $unknown;
1498
- $arc_but = true;
1499
-
1500
- $archived = false;
1501
- }
1502
-
1503
- //* Adds notice for global archive indexing options.
1504
- if ( $args['is_term'] ) {
1505
- /**
1506
- * @staticvar bool $checked
1507
- * @staticvar string $label
1508
- */
1509
- static $checked = null;
1510
-
1511
- if ( ! isset( $checked ) ) {
1512
- //* Fetch whether it's checked.
1513
- $checked = $this->the_seo_bar_archive_robots_options( 'noarchive' );
1514
- }
1515
-
1516
- if ( $checked ) {
1517
- $but_and = isset( $arc_but ) ? $and_i18n : $but_i18n;
1518
- $label = $this->get_tax_type_label( $this->fetch_the_term( $args['post_id'] )->taxonomy, false );
1519
-
1520
- /* translators: 1: But or And, 2: Current taxonomy term plural label */
1521
- $arc_notice .= '<br>' . sprintf( \esc_attr__( '%1$s archiving for %2$s have been discouraged.', 'autodescription' ), $but_and, $label );
1522
- $arc_class = $unknown;
1523
- $arc_but = true;
1524
-
1525
- $archived = false;
1526
- }
1527
- }
1528
-
1529
- //* Adds notice for robots post type settings.
1530
- static $pt_checked;
1531
- if ( ! isset( $pt_checked ) ) {
1532
- $_setting = $this->get_option( $this->get_robots_post_type_option_id( 'noarchive' ) );
1533
- $pt_checked = ! empty( $_setting[ $args['post_type'] ] );
1534
- }
1535
- if ( $pt_checked ) {
1536
- $but_and = isset( $but ) ? $and_i18n : $but_i18n;
1537
- /* translators: %s = But or And */
1538
- $arc_notice .= '<br>' . sprintf( \esc_attr__( '%s this post type is discouraging from being archived.', 'autodescription' ), $but_and );
1539
- $arc_class = $unknown;
1540
- $but = true;
1541
- }
1542
-
1543
- if ( false === $this->is_blog_public() ) {
1544
- //* Make it "and" if archiving has not been discouraged otherwise.
1545
- $but_and = $archived || ! isset( $arc_but ) ? $and_i18n : $but_i18n;
1546
-
1547
- /* translators: %s = But or And */
1548
- $arc_notice .= '<br>' . sprintf( \esc_attr__( "%s the blog isn't set to public. This means WordPress allows the blog to be archived regardless.", 'autodescription' ), $but_and );
1549
- $arc_but = true;
1550
-
1551
- $arc_class = $archived ? $arc_class : $okay;
1552
- $archived = true;
1553
- }
1554
-
1555
- $arc_wrap_args = [
1556
- 'indicator' => $archive_short,
1557
- 'notice' => $arc_notice,
1558
- 'width' => $ad_125,
1559
- 'class' => $arc_class,
1560
- ];
1561
-
1562
- $archive_notice = $this->wrap_the_seo_bar_block( $arc_wrap_args );
1563
-
1564
- return $archive_notice;
1565
- }
1566
-
1567
- /**
1568
- * Render the SEO bar redirect block and notice.
1569
- *
1570
- * @since 2.6.0
1571
- *
1572
- * @param array $args
1573
- * @return string The SEO Bar Redirect Block
1574
- */
1575
- protected function the_seo_bar_redirect_notice( $args ) {
1576
-
1577
- if ( $args['is_term'] ) {
1578
- //* No redirection on taxonomies (yet).
1579
- $redirect_notice = '';
1580
- } else {
1581
- //* Pretty much outputs that it's not being redirected.
1582
-
1583
- $post = $args['post_i18n'];
1584
-
1585
- $classes = $this->get_the_seo_bar_classes();
1586
-
1587
- $i18n = $this->get_the_seo_bar_i18n();
1588
- $redirect_i18n = $i18n['redirect'];
1589
- $redirect_short = $i18n['redirect_short'];
1590
-
1591
- /* translators:* %s = post type name */
1592
- $red_notice = $redirect_i18n . ' ' . sprintf( \esc_attr__( "%s isn't being redirected.", 'autodescription' ), $post );
1593
- $red_class = $classes['good'];
1594
-
1595
- $red_wrap_args = [
1596
- 'indicator' => $redirect_short,
1597
- 'notice' => $red_notice,
1598
- 'width' => $classes['12.5%'],
1599
- 'class' => $red_class,
1600
- ];
1601
-
1602
- $redirect_notice = $this->wrap_the_seo_bar_block( $red_wrap_args );
1603
- }
1604
-
1605
- return $redirect_notice;
1606
- }
1607
-
1608
- /**
1609
- * Render the SEO bar when the page/term is blocked.
1610
- *
1611
- * @since 2.6.0
1612
- *
1613
- * @param array $args {
1614
- * $is_term => bool,
1615
- * $redirect => bool,
1616
- * $noindex => bool,
1617
- * $post_i18n => string
1618
- * }
1619
- * @return string The SEO Bar
1620
- */
1621
- protected function the_seo_bar_blocked( $args ) {
1622
-
1623
- $classes = $this->get_the_seo_bar_classes();
1624
- $i18n = $this->get_the_seo_bar_i18n();
1625
-
1626
- //? extract().
1627
- foreach ( $args as $k => $v ) $$k = $v;
1628
-
1629
- if ( $redirect && $noindex ) {
1630
- //* Redirect and noindex found, why bother showing SEO info?
1631
-
1632
- /* translators:* %s = post type name */
1633
- $red_notice = $i18n['redirect'] . ' ' . sprintf( \esc_attr__( '%s is being redirected. This means no SEO values have to be set.', 'autodescription' ), $post_i18n );
1634
- $red_class = $classes['unknown'];
1635
-
1636
- /* translators:* %s = post type name */
1637
- $noi_notice = $i18n['index'] . ' ' . sprintf( \esc_attr__( '%s is not being indexed. This means no SEO values have to be set.', 'autodescription' ), $post_i18n );
1638
- $noi_class = $classes['unknown'];
1639
-
1640
- $red_wrap_args = [
1641
- 'indicator' => $i18n['redirect_short'],
1642
- 'notice' => $red_notice,
1643
- 'width' => '',
1644
- 'class' => $red_class,
1645
- ];
1646
-
1647
- $noi_wrap_args = [
1648
- 'indicator' => $i18n['index_short'],
1649
- 'notice' => $noi_notice,
1650
- 'width' => '',
1651
- 'class' => $noi_class,
1652
- ];
1653
-
1654
- $redirect_notice = $this->wrap_the_seo_bar_block( $red_wrap_args );
1655
- $noindex_notice = $this->wrap_the_seo_bar_block( $noi_wrap_args );
1656
-
1657
- $content = $redirect_notice . $noindex_notice;
1658
-
1659
- return $this->get_the_seo_bar_wrap( $content, $is_term );
1660
- } elseif ( $redirect && false === $noindex ) {
1661
- //* Redirect found, why bother showing SEO info?
1662
-
1663
- /* translators:* %s = post type name */
1664
- $red_notice = $i18n['redirect'] . ' ' . sprintf( \esc_attr__( '%s is being redirected. This means no SEO values have to be set.', 'autodescription' ), $post_i18n );
1665
- $red_class = $classes['unknown'];
1666
-
1667
- $red_wrap_args = [
1668
- 'indicator' => $i18n['redirect_short'],
1669
- 'notice' => $red_notice,
1670
- 'width' => '',
1671
- 'class' => $red_class,
1672
- ];
1673
-
1674
- $redirect_notice = $this->wrap_the_seo_bar_block( $red_wrap_args );
1675
-
1676
- return $this->get_the_seo_bar_wrap( $redirect_notice, $is_term );
1677
- } elseif ( $noindex && false === $redirect ) {
1678
- //* Noindex found, why bother showing SEO info?
1679
-
1680
- /* translators:* %s = post type name */
1681
- $noi_notice = $i18n['index'] . ' ' . sprintf( \esc_attr__( '%s is not being indexed. This means no SEO values have to be set.', 'autodescription' ), $post_i18n );
1682
- $noi_class = $classes['unknown'];
1683
-
1684
- $noi_wrap_args = [
1685
- 'indicator' => $i18n['index_short'],
1686
- 'notice' => $noi_notice,
1687
- 'width' => '',
1688
- 'class' => $noi_class,
1689
- ];
1690
-
1691
- $noindex_notice = $this->wrap_the_seo_bar_block( $noi_wrap_args );
1692
-
1693
- return $this->get_the_seo_bar_wrap( $noindex_notice, $is_term );
1694
- }
1695
-
1696
- return '';
1697
- }
1698
-
1699
- /**
1700
- * Title Length notices.
1701
- *
1702
- * @since 2.6.0
1703
- * @since 3.1.0 Now uses the new guidelines via a filterable function.
1704
- *
1705
- * @param int $tit_len The Title length
1706
- * @param string $class The Current Title notification class.
1707
- * @return array {
1708
- * string $notice => The notice,
1709
- * string $class => The class,
1710
- * bool $but => Whether we should use but or and,
1711
- * }
1712
- */
1713
- protected function get_the_seo_bar_title_length_warning( $tit_len, $class ) {
1714
-
1715
- $classes = $this->get_the_seo_bar_classes();
1716
- $i18n = $this->get_the_seo_bar_i18n();
1717
- $guidelines = $this->get_input_guidelines()['title']['search']['chars'];
1718
-
1719
- $but = false;
1720
-
1721
- if ( ! $tit_len ) {
1722
- $notice = $i18n['length_empty'];
1723
- $class = $classes['unknown'];
1724
- } elseif ( $tit_len < $guidelines['lower'] ) {
1725
- $notice = $i18n['length_far_too_short'];
1726
- $class = $classes['bad'];
1727
- } elseif ( $tit_len < $guidelines['goodLower'] ) {
1728
- $notice = $i18n['length_too_short'];
1729
- $class = $classes['okay'];
1730
- } elseif ( $tit_len > $guidelines['upper'] ) {
1731
- $notice = $i18n['length_far_too_long'];
1732
- $class = $classes['bad'];
1733
- } elseif ( $tit_len > $guidelines['goodUpper'] ) {
1734
- $notice = $i18n['length_too_long'];
1735
- $class = $classes['okay'];
1736
- } else {
1737
- $notice = $i18n['length_good'];
1738
- $class = $classes['good'];
1739
- $but = true;
1740
- }
1741
-
1742
- return compact( 'notice', 'class', 'but' );
1743
- }
1744
-
1745
- /**
1746
- * Returns an array of the classes used for CSS within The SEO Bar.
1747
- *
1748
- * @since 2.6.0
1749
- * @since 3.1.0 1. Removed 'pill' index.
1750
- * 2. Added 15% and 12.5% flex widths.
1751
- * 3. Removed all other widths.
1752
- *
1753
- * @return array The class names.
1754
- */
1755
- public function get_the_seo_bar_classes() {
1756
- return [
1757
- 'bad' => 'tsf-seo-bar-bad',
1758
- 'okay' => 'tsf-seo-bar-okay',
1759
- 'good' => 'tsf-seo-bar-good',
1760
- 'unknown' => 'tsf-seo-bar-unknown',
1761
-
1762
- '15%' => 'tsf-seo-bar-15',
1763
- '12.5%' => 'tsf-seo-bar-12-5',
1764
- ];
1765
- }
1766
-
1767
- /**
1768
- * Returns an array of the i18n notices for The SEO Bar.
1769
- *
1770
- * @staticvar array $i18n
1771
- * @since 2.6.0
1772
- *
1773
- * @return array The i18n sentences.
1774
- */
1775
- public function get_the_seo_bar_i18n() {
1776
-
1777
- static $i18n = null;
1778
-
1779
- if ( isset( $i18n ) )
1780
- return $i18n;
1781
-
1782
- $guideline_i18n = $this->get_input_guidelines_i18n()['long'];
1783
- // phpcs:disable WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned -- precision alignment OK.
1784
- return $i18n = [
1785
- 'title' => \esc_attr__( 'Title:', 'autodescription' ),
1786
- 'description' => \esc_attr__( 'Description:', 'autodescription' ),
1787
- 'index' => \esc_attr__( 'Index:', 'autodescription' ),
1788
- 'follow' => \esc_attr__( 'Follow:', 'autodescription' ),
1789
- 'archive' => \esc_attr__( 'Archive:', 'autodescription' ),
1790
- 'redirect' => \esc_attr__( 'Redirect:', 'autodescription' ),
1791
-
1792
- 'generated' => \esc_attr__( 'Generated: Automatically generated.', 'autodescription' ),
1793
-
1794
- 'generated_short' => \esc_html_x( 'G', 'Generated', 'autodescription' ),
1795
- 'title_short' => \esc_html_x( 'T', 'Title', 'autodescription' ),
1796
- 'description_short' => \esc_html_x( 'D', 'Description', 'autodescription' ),
1797
- 'index_short' => \esc_html_x( 'I', 'no-Index', 'autodescription' ),
1798
- 'follow_short' => \esc_html_x( 'F', 'no-Follow', 'autodescription' ),
1799
- 'archive_short' => \esc_html_x( 'A', 'no-Archive', 'autodescription' ),
1800
- 'redirect_short' => \esc_html_x( 'R', 'Redirect', 'autodescription' ),
1801
-
1802
- 'but' => \esc_attr_x( 'But', 'But there are...', 'autodescription' ),
1803
- 'and' => \esc_attr_x( 'And', 'And there are...', 'autodescription' ),
1804
-
1805
- 'length_empty' => $guideline_i18n['empty'],
1806
- 'length_far_too_short' => $guideline_i18n['farTooShort'],
1807
- 'length_too_short' => $guideline_i18n['tooShort'],
1808
- 'length_too_long' => $guideline_i18n['tooLong'],
1809
- 'length_far_too_long' => $guideline_i18n['farTooLong'],
1810
- 'length_good' => $guideline_i18n['good'],
1811
- ];
1812
- // phpcs:enable WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
1813
- }
1814
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/classes/feed.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -33,27 +35,6 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
33
  */
34
  class Feed extends Cache {
35
 
36
- /**
37
- * Initializes feed actions and hooks.
38
- *
39
- * @since 2.9.0
40
- * @access private
41
- *
42
- * @return void Early if this request isn't for a feed.
43
- */
44
- public function _init_feed_output() {
45
-
46
- if ( ! $this->is_feed() )
47
- return;
48
-
49
- \add_filter( 'the_content_feed', [ $this, 'the_content_feed' ], 10, 2 );
50
-
51
- //* Only add the feed link to the excerpt if we're only building excerpts.
52
- if ( $this->rss_uses_excerpt() )
53
- \add_filter( 'the_excerpt_rss', [ $this, 'the_content_feed' ], 10, 1 );
54
-
55
- }
56
-
57
  /**
58
  * Determines whether the WordPress excerpt RSS feed option is used.
59
  *
@@ -73,14 +54,13 @@ class Feed extends Cache {
73
  *
74
  * @since 2.5.2
75
  *
76
- * @param $content The feed's content.
77
- * @param $feed_type The feed type (not used in excerpted content)
78
  * @return string The modified feed entry.
79
  */
80
  public function the_content_feed( $content = '', $feed_type = null ) {
81
 
82
- if ( ! $content )
83
- return '';
84
 
85
  /**
86
  * Don't alter already-excerpts or descriptions.
@@ -101,14 +81,14 @@ class Feed extends Cache {
101
  * Converts feed content to excerpt.
102
  *
103
  * @since 2.9.0
 
104
  *
105
  * @param string $content The full feed entry content.
106
  * @return string The excerpted feed.
107
  */
108
  protected function convert_feed_entry_to_excerpt( $content = '' ) {
109
 
110
- if ( ! $content )
111
- return '';
112
 
113
  //* Strip all code and lines.
114
  $excerpt = $this->s_excerpt_raw( $content, false );
@@ -126,20 +106,19 @@ class Feed extends Cache {
126
 
127
  if ( 0 === strpos( $content, '<h2>' ) ) {
128
  //* Add the h2 title back
129
- $h2_end = mb_strpos( $content, '</h2>' );
130
 
131
  if ( false !== $h2_end ) {
132
- //* Start of content, plus <h2>
133
- $h2_start = 4;
134
  //* Remove the length of <h2>, again.
135
  $h2_end = $h2_end - $h2_start;
136
 
137
  //* Fetch h2 content.
138
- $h2_content = mb_substr( $content, $h2_start, $h2_end );
139
 
140
  //* Remove the H2 content from the excerpt.
141
- $count = 1;
142
- $excerpt = str_replace( $h2_content, '', $excerpt, $count );
143
 
144
  //* Wrap h2 content in h2 tags.
145
  $h2_output = '<h2>' . $h2_content . '</h2>' . PHP_EOL;
@@ -169,8 +148,11 @@ class Feed extends Cache {
169
  'the_seo_framework_feed_source_link_text',
170
  \_x( 'Source', 'The content source', 'autodescription' )
171
  );
172
- $content = '<p><a href="' . \esc_url( \get_permalink() ) . '" rel="nofollow">' . \esc_html( $source_i18n ) . '</a></p>';
173
 
174
- return $content;
 
 
 
 
175
  }
176
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Feed
4
+ * @subpackage The_SEO_Framework\Feed
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
35
  */
36
  class Feed extends Cache {
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  /**
39
  * Determines whether the WordPress excerpt RSS feed option is used.
40
  *
54
  *
55
  * @since 2.5.2
56
  *
57
+ * @param string $content The feed's content.
58
+ * @param null|string $feed_type The feed type (not used in excerpted content)
59
  * @return string The modified feed entry.
60
  */
61
  public function the_content_feed( $content = '', $feed_type = null ) {
62
 
63
+ if ( ! $content ) return '';
 
64
 
65
  /**
66
  * Don't alter already-excerpts or descriptions.
81
  * Converts feed content to excerpt.
82
  *
83
  * @since 2.9.0
84
+ * @since 4.0.0 No longer uses mbstring for html tagging, it was redundant as we were looking for ASCII characters.
85
  *
86
  * @param string $content The full feed entry content.
87
  * @return string The excerpted feed.
88
  */
89
  protected function convert_feed_entry_to_excerpt( $content = '' ) {
90
 
91
+ if ( ! $content ) return '';
 
92
 
93
  //* Strip all code and lines.
94
  $excerpt = $this->s_excerpt_raw( $content, false );
106
 
107
  if ( 0 === strpos( $content, '<h2>' ) ) {
108
  //* Add the h2 title back
109
+ $h2_end = strpos( $content, '</h2>' );
110
 
111
  if ( false !== $h2_end ) {
112
+ //* Start of content, plus strlen( '<h2>' )
113
+ $h2_start = strlen( '<h2>' );
114
  //* Remove the length of <h2>, again.
115
  $h2_end = $h2_end - $h2_start;
116
 
117
  //* Fetch h2 content.
118
+ $h2_content = substr( $content, $h2_start, $h2_end );
119
 
120
  //* Remove the H2 content from the excerpt.
121
+ $excerpt = str_replace( $h2_content, '', $excerpt );
 
122
 
123
  //* Wrap h2 content in h2 tags.
124
  $h2_output = '<h2>' . $h2_content . '</h2>' . PHP_EOL;
148
  'the_seo_framework_feed_source_link_text',
149
  \_x( 'Source', 'The content source', 'autodescription' )
150
  );
 
151
 
152
+ return sprintf(
153
+ '<p><a href="%s" rel="nofollow">%s</a></p>',
154
+ \esc_url( \get_permalink() ),
155
+ \esc_html( $source_i18n )
156
+ );
157
  }
158
  }
inc/classes/generate-description.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -47,8 +49,10 @@ class Generate_Description extends Generate {
47
  */
48
  public function get_description( $args = null, $escape = true ) {
49
 
 
50
  $desc = $this->get_description_from_custom_field( $args, false )
51
- ?: $this->get_generated_description( $args, false ); // precision alignment ok.
 
52
 
53
  return $escape ? $this->escape_description( $desc ) : $desc;
54
  }
@@ -69,8 +73,10 @@ class Generate_Description extends Generate {
69
  */
70
  public function get_open_graph_description( $args = null, $escape = true ) {
71
 
 
72
  $desc = $this->get_open_graph_description_from_custom_field( $args, false )
73
- ?: $this->get_generated_open_graph_description( $args, false ); // precision alignment ok.
 
74
 
75
  return $escape ? $this->escape_description( $desc ) : $desc;
76
  }
@@ -105,6 +111,7 @@ class Generate_Description extends Generate {
105
  *
106
  * @since 3.1.0
107
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
 
108
  * @see $this->get_open_graph_description()
109
  * @see $this->get_open_graph_description_from_custom_field()
110
  *
@@ -113,22 +120,24 @@ class Generate_Description extends Generate {
113
  protected function get_custom_open_graph_description_from_query() {
114
 
115
  $desc = '';
116
-
117
  if ( $this->is_real_front_page() ) {
118
  if ( $this->is_static_frontpage() ) {
119
  $desc = $this->get_option( 'homepage_og_description' )
120
- ?: $this->get_custom_field( '_open_graph_description' )
121
- ?: $this->get_description_from_custom_field(); // precision alignment ok
122
  } else {
123
  $desc = $this->get_option( 'homepage_og_description' )
124
- ?: $this->get_description_from_custom_field(); // precision alignment ok.
125
  }
126
  } elseif ( $this->is_singular() ) {
127
- $desc = $this->get_custom_field( '_open_graph_description' )
128
- ?: $this->get_description_from_custom_field(); // precision alignment ok.
129
  } elseif ( $this->is_term_meta_capable() ) {
130
- $desc = $this->get_description_from_custom_field();
 
131
  }
 
132
 
133
  return $desc;
134
  }
@@ -140,6 +149,7 @@ class Generate_Description extends Generate {
140
  * @since 3.1.0
141
  * @since 3.2.2: 1. Now tests for the homepage as page prior getting custom field data.
142
  * 2. Now obtains custom field data for terms.
 
143
  * @see $this->get_open_graph_description()
144
  * @see $this->get_open_graph_description_from_custom_field()
145
  *
@@ -149,22 +159,24 @@ class Generate_Description extends Generate {
149
  protected function get_custom_open_graph_description_from_args( array $args ) {
150
 
151
  $desc = '';
152
-
153
  if ( $args['taxonomy'] ) {
154
- $desc = $this->get_description_from_custom_field( $args );
 
155
  } else {
156
  if ( $this->is_static_frontpage( $args['id'] ) ) {
157
  $desc = $this->get_option( 'homepage_og_description' )
158
- ?: $this->get_custom_field( '_open_graph_description', $args['id'] )
159
- ?: $this->get_description_from_custom_field( $args ); // precision alignment ok.
160
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
161
  $desc = $this->get_option( 'homepage_og_description' )
162
- ?: $this->get_description_from_custom_field( $args ); // precision alignment ok.
163
  } else {
164
- $desc = $this->get_custom_field( '_open_graph_description', $args['id'] )
165
- ?: $this->get_description_from_custom_field( $args ); // precision alignment ok.
166
  }
167
  }
 
168
 
169
  return $desc;
170
  }
@@ -186,8 +198,10 @@ class Generate_Description extends Generate {
186
  */
187
  public function get_twitter_description( $args = null, $escape = true ) {
188
 
 
189
  $desc = $this->get_twitter_description_from_custom_field( $args, false )
190
- ?: $this->get_generated_twitter_description( $args, false ); // precision alignment ok.
 
191
 
192
  return $escape ? $this->escape_description( $desc ) : $desc;
193
  }
@@ -223,6 +237,7 @@ class Generate_Description extends Generate {
223
  * @since 3.1.0
224
  * @since 3.2.2: 1. Now tests for the homepage as page prior getting custom field data.
225
  * 2. Now obtains custom field data for terms.
 
226
  * @see $this->get_twitter_description()
227
  * @see $this->get_twitter_description_from_custom_field()
228
  *
@@ -231,26 +246,33 @@ class Generate_Description extends Generate {
231
  protected function get_custom_twitter_description_from_query() {
232
 
233
  $desc = '';
234
-
235
  if ( $this->is_real_front_page() ) {
236
  if ( $this->is_static_frontpage() ) {
237
  $desc = $this->get_option( 'homepage_twitter_description' )
238
- ?: $this->get_custom_field( '_twitter_description' )
239
- ?: $this->get_option( 'homepage_og_description' )
240
- ?: $this->get_custom_field( '_open_graph_description' )
241
- ?: $this->get_description_from_custom_field(); // precision alignment ok.
 
242
  } else {
243
  $desc = $this->get_option( 'homepage_twitter_description' )
244
  ?: $this->get_option( 'homepage_og_description' )
245
- ?: $this->get_description_from_custom_field(); // precision alignment ok.
 
246
  }
247
  } elseif ( $this->is_singular() ) {
248
- $desc = $this->get_custom_field( '_twitter_description' )
249
- ?: $this->get_custom_field( '_open_graph_description' )
250
- ?: $this->get_description_from_custom_field(); // precision alignment ok.
 
251
  } elseif ( $this->is_term_meta_capable() ) {
252
- $desc = $this->get_description_from_custom_field();
 
 
 
253
  }
 
254
 
255
  return $desc;
256
  }
@@ -262,6 +284,7 @@ class Generate_Description extends Generate {
262
  * @since 3.1.0
263
  * @since 3.2.2: 1. Now tests for the homepage as page prior getting custom field data.
264
  * 2. Now obtains custom field data for terms.
 
265
  * @see $this->get_twitter_description()
266
  * @see $this->get_twitter_description_from_custom_field()
267
  *
@@ -270,27 +293,33 @@ class Generate_Description extends Generate {
270
  */
271
  protected function get_custom_twitter_description_from_args( array $args ) {
272
 
273
- $desc = '';
274
-
275
  if ( $args['taxonomy'] ) {
276
- $desc = $this->get_description_from_custom_field( $args );
 
 
 
277
  } else {
278
  if ( $this->is_static_frontpage( $args['id'] ) ) {
279
  $desc = $this->get_option( 'homepage_twitter_description' )
280
- ?: $this->get_custom_field( '_twitter_description', $args['id'] )
281
  ?: $this->get_option( 'homepage_og_description' )
282
- ?: $this->get_custom_field( '_open_graph_description', $args['id'] )
283
- ?: $this->get_description_from_custom_field( $args ); // precision alignment ok.
 
284
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
285
  $desc = $this->get_option( 'homepage_twitter_description' )
286
  ?: $this->get_option( 'homepage_og_description' )
287
- ?: $this->get_description_from_custom_field( $args ); // precision alignment ok.
 
288
  } else {
289
- $desc = $this->get_custom_field( '_twitter_description', $args['id'] )
290
- ?: $this->get_custom_field( '_open_graph_description', $args['id'] )
291
- ?: $this->get_description_from_custom_field( $args ); // precision alignment ok.
 
292
  }
293
  }
 
294
 
295
  return $desc;
296
  }
@@ -322,12 +351,12 @@ class Generate_Description extends Generate {
322
  }
323
 
324
  /**
325
- * Filters the description from custom field, if any.
326
  * @since 2.9.0
327
  * @since 3.0.6 1. Duplicated from $this->generate_description() (deprecated)
328
  * 2. Removed all arguments but the 'id' argument.
329
- * @param string $desc The description.
330
- * @param array $args The description arguments.
 
331
  */
332
  $desc = (string) \apply_filters( 'the_seo_framework_custom_field_description', $desc, $args );
333
 
@@ -348,20 +377,21 @@ class Generate_Description extends Generate {
348
 
349
  $desc = '';
350
 
 
351
  if ( $this->is_real_front_page() ) {
352
  if ( $this->is_static_frontpage() ) {
353
  $desc = $this->get_option( 'homepage_description' )
354
- ?: $this->get_custom_field( '_genesis_description' )
355
- ?: ''; // precision alignment ok.
356
  } else {
357
  $desc = $this->get_option( 'homepage_description' ) ?: '';
358
  }
359
  } elseif ( $this->is_singular() ) {
360
- $desc = $this->get_custom_field( '_genesis_description' ) ?: '';
361
  } elseif ( $this->is_term_meta_capable() ) {
362
- $data = $this->get_term_meta( $this->get_the_real_ID() );
363
- $desc = ! empty( $data['description'] ) ? $data['description'] : '';
364
  }
 
365
 
366
  return $desc;
367
  }
@@ -379,22 +409,21 @@ class Generate_Description extends Generate {
379
  */
380
  protected function get_custom_description_from_args( array $args ) {
381
 
382
- $desc = '';
383
-
384
  if ( $args['taxonomy'] ) {
385
- $data = $this->get_term_meta( $args['id'] );
386
- $desc = ! empty( $data['description'] ) ? $data['description'] : '';
387
  } else {
388
  if ( $this->is_static_frontpage( $args['id'] ) ) {
389
  $desc = $this->get_option( 'homepage_description' )
390
- ?: $this->get_custom_field( '_genesis_description', $args['id'] )
391
- ?: ''; // Precision alignment ok.
392
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
393
  $desc = $this->get_option( 'homepage_description' ) ?: '';
394
  } else {
395
- $desc = $this->get_custom_field( '_genesis_description', $args['id'] ) ?: '';
396
  }
397
  }
 
398
 
399
  return $desc;
400
  }
@@ -423,12 +452,10 @@ class Generate_Description extends Generate {
423
  if ( ! $this->is_auto_description_enabled( $args ) ) return '';
424
 
425
  if ( null === $args ) {
426
- $excerpt = $this->get_description_excerpt_from_query();
427
- $_filter_id = $this->get_the_real_ID();
428
  } else {
429
  $this->fix_generation_args( $args );
430
- $_filter_id = $args['id'];
431
- $excerpt = $this->get_description_excerpt_from_args( $args );
432
  }
433
 
434
  if ( ! in_array( $type, [ 'opengraph', 'twitter', 'search' ], true ) )
@@ -437,10 +464,14 @@ class Generate_Description extends Generate {
437
  /**
438
  * @since 2.9.0
439
  * @since 3.1.0 No longer passes 3rd and 4th parameter.
440
- * @param string $excerpt The excerpt to use.
441
- * @param int $page_id The current page/term ID
 
 
 
 
442
  */
443
- $excerpt = (string) \apply_filters( 'the_seo_framework_fetched_description_excerpt', $excerpt, $_filter_id );
444
 
445
  $excerpt = $this->trim_excerpt(
446
  html_entity_decode( $excerpt, ENT_QUOTES | ENT_COMPAT, 'UTF-8' ),
@@ -449,11 +480,11 @@ class Generate_Description extends Generate {
449
  );
450
 
451
  /**
452
- * Filters the generated description, if any.
453
  * @since 2.9.0
454
  * @since 3.1.0 No longer passes 3rd and 4th parameter.
455
- * @param string $description The description.
456
- * @param array|null $args The description arguments.
 
457
  */
458
  $desc = (string) \apply_filters( 'the_seo_framework_generated_description', $excerpt, $args );
459
 
@@ -535,7 +566,7 @@ class Generate_Description extends Generate {
535
  if ( $args['taxonomy'] ) {
536
  $excerpt = $this->get_archival_description_excerpt( \get_term( $args['id'], $args['taxonomy'] ) );
537
  } else {
538
- if ( $this->is_blog_page( $args['id'] ) ) {
539
  $excerpt = $this->get_blog_page_description_excerpt();
540
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
541
  $excerpt = $this->get_front_page_description_excerpt();
@@ -582,8 +613,9 @@ class Generate_Description extends Generate {
582
  * Returns a description excerpt for archives.
583
  *
584
  * @since 3.1.0
 
585
  *
586
- * @param null|\WP_Term $term
587
  * @return string
588
  */
589
  protected function get_archival_description_excerpt( $term = null ) {
@@ -600,6 +632,7 @@ class Generate_Description extends Generate {
600
 
601
  /**
602
  * @since 3.1.0
 
603
  * @param string $excerpt The short circuit excerpt.
604
  * @param \WP_Term $term The Term object.
605
  */
@@ -613,9 +646,11 @@ class Generate_Description extends Generate {
613
  $excerpt = ! empty( $term->description ) ? $this->s_description_raw( $term->description ) : '';
614
  } else {
615
  if ( $this->is_category() || $this->is_tag() || $this->is_tax() ) {
 
 
616
  $excerpt = ! empty( $term->description ) ? $this->s_description_raw( $term->description ) : '';
617
  } elseif ( $this->is_author() ) {
618
- $excerpt = $this->s_description_raw( \get_the_author_meta( 'description', (int) \get_query_var( 'author' ) ) );
619
  } elseif ( \is_post_type_archive() ) {
620
  // TODO
621
  $excerpt = '';
@@ -647,7 +682,7 @@ class Generate_Description extends Generate {
647
  }
648
 
649
  /**
650
- * Returns additions for "Title on Blogname".
651
  *
652
  * @since 3.1.0
653
  * @since 3.2.0 : 1. Now no longer listens to options.
@@ -664,8 +699,8 @@ class Generate_Description extends Generate {
664
 
665
  $this->fix_generation_args( $args );
666
 
667
- if ( $this->is_blog_page( $args['id'] ) ) {
668
- $title = $this->get_raw_generated_title( $args );
669
  /* translators: %s = Blog page title. Front-end output. */
670
  $title = sprintf( \__( 'Latest posts: %s', 'autodescription' ), $title );
671
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
@@ -683,6 +718,71 @@ class Generate_Description extends Generate {
683
  return trim( sprintf( \__( '%1$s %2$s %3$s', 'autodescription' ), $title, $on, $blogname ) );
684
  }
685
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
686
  /**
687
  * Trims the excerpt by word and determines sentence stops.
688
  *
@@ -692,10 +792,16 @@ class Generate_Description extends Generate {
692
  * 3. Now has unicode support for sentence closing.
693
  * 4. Now strips last three words when preceded by a sentence closing separator.
694
  * 5. Now always leads with (inviting) dots, even if the excerpt is shorter than $max_char_length.
 
 
 
695
  * @see https://secure.php.net/manual/en/regexp.reference.unicode.php
696
  *
 
 
 
697
  * @param string $excerpt The untrimmed excerpt.
698
- * @param int $depr The current excerpt length. No longer needed.
699
  * @param int $max_char_length At what point to shave off the excerpt.
700
  * @return string The trimmed excerpt.
701
  */
@@ -705,22 +811,61 @@ class Generate_Description extends Generate {
705
  preg_match( sprintf( '/.{0,%d}([^\P{Po}\'\"]|\p{Z}|$){1}/su', $max_char_length ), trim( $excerpt ), $matches );
706
  $excerpt = isset( $matches[0] ) ? ( $matches[0] ?: '' ) : '';
707
 
708
- //* Remove trailing/leading commas and spaces.
709
- $excerpt = trim( $excerpt, ' ,' );
710
-
711
- //* Test if there's punctuation with something trailing. The next regex will be a wild-goose chase otherwise.
712
- preg_match( '/.[^\P{Po}\'\"]\p{Z}*\w/su', $excerpt, $matches );
713
- if ( $matches ) {
714
- //* Find words that are leading after a dot. If there are 3 or fewer, trim them.
715
- //= super fast when there's punctuation with something trailing:
716
- preg_match( '/(.+)(([^\P{Po}\'\"])\p{Z}*(\w+\p{Z}*){1,3})(.+)?/su', $excerpt, $matches );
717
- // If $matches[5] is set, then there are more than 3 words...
718
- if ( isset( $matches[1], $matches[3] ) && empty( $matches[5] ) ) {
719
- $excerpt = $matches[1] . $matches[3];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
720
  }
 
 
 
721
  }
 
 
 
 
 
722
 
723
- //* Remove leading commas again.
724
  $excerpt = rtrim( $excerpt, ' ,' );
725
 
726
  if ( ';' === substr( $excerpt, -1 ) ) {
@@ -764,8 +909,9 @@ class Generate_Description extends Generate {
764
  * @since 2.5.0
765
  * @since 3.0.0 Now passes $args as the second parameter.
766
  * @since 3.1.0 Now listens to option.
767
- * @param bool $autodescription Enable or disable the automated descriptions.
768
- * @param array $args The description arguments.
 
769
  */
770
  return (bool) \apply_filters_ref_array(
771
  'the_seo_framework_enable_auto_description',
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Generate_Description
4
+ * @subpackage The_SEO_Framework\Getters\Description
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
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
  }
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
  }
111
  *
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
  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
  }
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
  *
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
  }
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
  }
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
  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
  }
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
  *
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'] ) ) {
304
  $desc = $this->get_option( 'homepage_twitter_description' )
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
  }
351
  }
352
 
353
  /**
 
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
 
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' )
384
+ ?: $this->get_post_meta_item( '_genesis_description' )
385
+ ?: '';
386
  } else {
387
  $desc = $this->get_option( 'homepage_description' ) ?: '';
388
  }
389
  } elseif ( $this->is_singular() ) {
390
+ $desc = $this->get_post_meta_item( '_genesis_description' ) ?: '';
391
  } elseif ( $this->is_term_meta_capable() ) {
392
+ $desc = $this->get_term_meta_item( 'description' ) ?: '';
 
393
  }
394
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
395
 
396
  return $desc;
397
  }
409
  */
410
  protected function get_custom_description_from_args( array $args ) {
411
 
412
+ // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
 
413
  if ( $args['taxonomy'] ) {
414
+ $desc = $this->get_term_meta_item( 'description', $args['id'] ) ?: '';
 
415
  } else {
416
  if ( $this->is_static_frontpage( $args['id'] ) ) {
417
  $desc = $this->get_option( 'homepage_description' )
418
+ ?: $this->get_post_meta_item( '_genesis_description', $args['id'] )
419
+ ?: '';
420
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
421
  $desc = $this->get_option( 'homepage_description' ) ?: '';
422
  } else {
423
+ $desc = $this->get_post_meta_item( '_genesis_description', $args['id'] ) ?: '';
424
  }
425
  }
426
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
427
 
428
  return $desc;
429
  }
452
  if ( ! $this->is_auto_description_enabled( $args ) ) return '';
453
 
454
  if ( null === $args ) {
455
+ $excerpt = $this->get_description_excerpt_from_query();
 
456
  } else {
457
  $this->fix_generation_args( $args );
458
+ $excerpt = $this->get_description_excerpt_from_args( $args );
 
459
  }
460
 
461
  if ( ! in_array( $type, [ 'opengraph', 'twitter', 'search' ], true ) )
464
  /**
465
  * @since 2.9.0
466
  * @since 3.1.0 No longer passes 3rd and 4th parameter.
467
+ * @since 4.0.0 1. Deprecated second parameter.
468
+ * 2. Added third parameter: $args.
469
+ * @param string $excerpt The excerpt to use.
470
+ * @param int $page_id Deprecated.
471
+ * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
472
+ * Is null when query is autodetermined.
473
  */
474
+ $excerpt = (string) \apply_filters( 'the_seo_framework_fetched_description_excerpt', $excerpt, 0, $args );
475
 
476
  $excerpt = $this->trim_excerpt(
477
  html_entity_decode( $excerpt, ENT_QUOTES | ENT_COMPAT, 'UTF-8' ),
480
  );
481
 
482
  /**
 
483
  * @since 2.9.0
484
  * @since 3.1.0 No longer passes 3rd and 4th parameter.
485
+ * @param string $desc The generated description.
486
+ * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
487
+ * Is null when query is autodetermined.
488
  */
489
  $desc = (string) \apply_filters( 'the_seo_framework_generated_description', $excerpt, $args );
490
 
566
  if ( $args['taxonomy'] ) {
567
  $excerpt = $this->get_archival_description_excerpt( \get_term( $args['id'], $args['taxonomy'] ) );
568
  } else {
569
+ if ( $this->is_blog_page_by_id( $args['id'] ) ) {
570
  $excerpt = $this->get_blog_page_description_excerpt();
571
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
572
  $excerpt = $this->get_front_page_description_excerpt();
613
  * Returns a description excerpt for archives.
614
  *
615
  * @since 3.1.0
616
+ * @since 4.0.0 Now processes HTML tags via s_excerpt_raw() for the author descriptions.
617
  *
618
+ * @param null|\WP_Term $term The term.
619
  * @return string
620
  */
621
  protected function get_archival_description_excerpt( $term = null ) {
632
 
633
  /**
634
  * @since 3.1.0
635
+ * @see `\the_seo_framework()->s_excerpt_raw()` to strip HTML tags neatly.
636
  * @param string $excerpt The short circuit excerpt.
637
  * @param \WP_Term $term The Term object.
638
  */
646
  $excerpt = ! empty( $term->description ) ? $this->s_description_raw( $term->description ) : '';
647
  } else {
648
  if ( $this->is_category() || $this->is_tag() || $this->is_tax() ) {
649
+ // WordPress DOES NOT allow HTML in term descriptions, not even if you're a super-administrator.
650
+ // See https://wpvulndb.com/vulnerabilities/9445. We won't parse HTMl tags unless WordPress adds native support.
651
  $excerpt = ! empty( $term->description ) ? $this->s_description_raw( $term->description ) : '';
652
  } elseif ( $this->is_author() ) {
653
+ $excerpt = $this->s_excerpt_raw( \get_the_author_meta( 'description', (int) \get_query_var( 'author' ) ) );
654
  } elseif ( \is_post_type_archive() ) {
655
  // TODO
656
  $excerpt = '';
682
  }
683
 
684
  /**
685
+ * Returns additions for "Title on Blog name".
686
  *
687
  * @since 3.1.0
688
  * @since 3.2.0 : 1. Now no longer listens to options.
699
 
700
  $this->fix_generation_args( $args );
701
 
702
+ if ( $this->is_blog_page_by_id( $args['id'] ) ) {
703
+ $title = $this->get_filtered_raw_generated_title( $args );
704
  /* translators: %s = Blog page title. Front-end output. */
705
  $title = sprintf( \__( 'Latest posts: %s', 'autodescription' ), $title );
706
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
718
  return trim( sprintf( \__( '%1$s %2$s %3$s', 'autodescription' ), $title, $on, $blogname ) );
719
  }
720
 
721
+ /**
722
+ * Fetches or parses the excerpt of the post.
723
+ *
724
+ * @since 1.0.0
725
+ * @since 2.8.2 : Added 4th parameter for escaping.
726
+ * @since 3.1.0 1. No longer returns anything for terms.
727
+ * 2. Now strips plausible embeds URLs.
728
+ * @since 4.0.1 The second parameter `$id` now defaults to int 0, instead of an empty string.
729
+ *
730
+ * @param string $excerpt The Excerpt.
731
+ * @param int $id The Post ID.
732
+ * @param null $deprecated No longer used.
733
+ * @param bool $escape Whether to escape the excerpt.
734
+ * @return string The trimmed excerpt.
735
+ */
736
+ public function get_excerpt_by_id( $excerpt = '', $id = 0, $deprecated = null, $escape = true ) {
737
+
738
+ if ( empty( $excerpt ) )
739
+ $excerpt = $this->fetch_excerpt( $id );
740
+
741
+ //* No need to parse an empty excerpt.
742
+ if ( ! $excerpt ) return '';
743
+
744
+ return $escape ? $this->s_excerpt( $excerpt ) : $this->s_excerpt_raw( $excerpt );
745
+ }
746
+
747
+ /**
748
+ * Fetches excerpt from post excerpt or fetches the full post content.
749
+ * Determines if a page builder is used to return an empty string.
750
+ * Does not sanitize output.
751
+ *
752
+ * @since 2.5.2
753
+ * @since 2.6.6 Detects Page builders.
754
+ * @since 3.1.0 1. No longer returns anything for terms.
755
+ * 2. Now strips plausible embeds URLs.
756
+ * @since 4.0.1 Now fetches the real ID when no post is supplied.
757
+ * Internally, this was never an issue. @see `$this->get_singular_description_excerpt()`
758
+ *
759
+ * @param \WP_Post|int|null $post The Post or Post ID. Leave null to get current post.
760
+ * @return string The excerpt.
761
+ */
762
+ public function fetch_excerpt( $post = null ) {
763
+
764
+ $post = \get_post( $post ?: $this->get_the_real_ID() );
765
+
766
+ /**
767
+ * @since 2.5.2
768
+ * Fetch custom excerpt, if not empty, from the post_excerpt field.
769
+ */
770
+ if ( ! empty( $post->post_excerpt ) ) {
771
+ $excerpt = $post->post_excerpt;
772
+ } elseif ( isset( $post->post_content ) ) {
773
+ $excerpt = $this->uses_page_builder( $post->ID ) ? '' : $post->post_content;
774
+
775
+ if ( $excerpt ) {
776
+ $excerpt = $this->strip_newline_urls( $excerpt );
777
+ $excerpt = $this->strip_paragraph_urls( $excerpt );
778
+ }
779
+ } else {
780
+ $excerpt = '';
781
+ }
782
+
783
+ return $excerpt;
784
+ }
785
+
786
  /**
787
  * Trims the excerpt by word and determines sentence stops.
788
  *
792
  * 3. Now has unicode support for sentence closing.
793
  * 4. Now strips last three words when preceded by a sentence closing separator.
794
  * 5. Now always leads with (inviting) dots, even if the excerpt is shorter than $max_char_length.
795
+ * @since 4.0.0 : 1. Now stops parsing earlier on failure.
796
+ * 2. Now performs faster queries.
797
+ * 3. Now maintains last sentence with closing punctuations.
798
  * @see https://secure.php.net/manual/en/regexp.reference.unicode.php
799
  *
800
+ * We use `[^\P{Po}\'\"]` because WordPress texturizes ' and " to fall under `\P{Po}`, while they don't untexturized.
801
+ * This is perfect. Please have the cortesy to credit us when taking it.
802
+ *
803
  * @param string $excerpt The untrimmed excerpt.
804
+ * @param int $depr The current excerpt length. No longer needed. Deprecated.
805
  * @param int $max_char_length At what point to shave off the excerpt.
806
  * @return string The trimmed excerpt.
807
  */
811
  preg_match( sprintf( '/.{0,%d}([^\P{Po}\'\"]|\p{Z}|$){1}/su', $max_char_length ), trim( $excerpt ), $matches );
812
  $excerpt = isset( $matches[0] ) ? ( $matches[0] ?: '' ) : '';
813
 
814
+ $excerpt = trim( $excerpt );
815
+
816
+ if ( ! $excerpt ) return '';
817
+
818
+ /**
819
+ * Note to self: Leading spaces will cause this regex to fail. So, trimming prior is advised.
820
+ *
821
+ * 1. Tests for punctuation at the start.
822
+ * 2. Tests for any punctuation leading, if not found: fail and commit.
823
+ * 3. Tests if first leading punctuation has nothing leading.
824
+ * 4. If not, grab everything, find the last punctiation.
825
+ * 5. Test if the last punctiation has nothing leading.
826
+ * 6. If something's leading, grab the first 3 words and follow words separately.
827
+ *
828
+ * Critically optimized, so the $matches don't make much sense. Bear with me:
829
+ *
830
+ * @param array $matches : {
831
+ * 0 : Full excerpt excluding leading punctuation. May be empty when no leading punctuation is found.
832
+ * 1 : Sentence before first punctuation.
833
+ * 2 : First trailing punctuation, plus everything trailing until end of sentence. (equals [3][4][5][6])
834
+ * 3 : If more than one punctuation is found, this is everything leading [1] until the final punctuation.
835
+ * 4 : Final punctuation found, excluding quote tags; trailing [3].
836
+ * 5 : All extraneous words trailing [4].
837
+ * 6 : Every 4th and later word trailing [4].
838
+ * }
839
+ */
840
+ preg_match(
841
+ '/(?:^\p{P}*)([\P{Po}\'\"]+\p{Z}*\w*)(*COMMIT)(\p{Po}$|(.+)?([^\P{Po}\'\"])((?:\p{Z}*(?:\w+\p{Z}*){1,3})(.+)?)?)/su',
842
+ $excerpt,
843
+ $matches
844
+ );
845
+
846
+ if ( isset( $matches[6] ) ) {
847
+ // Accept everything.
848
+ $excerpt = $matches[1] . $matches[2];
849
+ } elseif ( isset( $matches[5] ) ) {
850
+ // Last sentence is too short to make sense of. Trim it.
851
+ if ( isset( $matches[3] ) ) {
852
+ // More than one punctuation is found. Concatenate.
853
+ $excerpt = $matches[1] . $matches[3] . $matches[4];
854
+ } else {
855
+ // Only one complete sentence is found. Concatenate last punctuation.
856
+ $excerpt = $matches[1] . $matches[4];
857
  }
858
+ } elseif ( isset( $matches[2] ) ) { // [3] and [4] may also be set, containing series of punctuation.
859
+ // Only one complete sentence is found. Series of punctuation, if any, is added in [2].
860
+ $excerpt = $matches[1] . $matches[2];
861
  }
862
+ // elseif ( isset( $matches[1] ) ) {
863
+ // Unfortunately, impossible. `(*COMMIT)` destroys this. $excerpt remains unchanged.
864
+ // Leading punctuation may still be present.
865
+ // $excerpt = $matches[1];
866
+ // }
867
 
868
+ //* Remove leading commas and spaces.
869
  $excerpt = rtrim( $excerpt, ' ,' );
870
 
871
  if ( ';' === substr( $excerpt, -1 ) ) {
909
  * @since 2.5.0
910
  * @since 3.0.0 Now passes $args as the second parameter.
911
  * @since 3.1.0 Now listens to option.
912
+ * @param bool $autodescription Enable or disable the automated descriptions.
913
+ * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
914
+ * Is null when query is autodetermined.
915
  */
916
  return (bool) \apply_filters_ref_array(
917
  'the_seo_framework_enable_auto_description',
inc/classes/generate-image.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -33,766 +35,511 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
33
  class Generate_Image extends Generate_Url {
34
 
35
  /**
36
- * @since 2.7.0
37
- * @var array The registered image dimensions.
38
- */
39
- public $image_dimensions = [];
40
-
41
- /**
42
- * Registers image dimensions.
43
- *
44
- * When the `$uid` is already registered, then the dimensions registered first
45
- * will be used.
46
- *
47
- * @since 3.1.0
48
- * @uses $this->image_dimensions
49
- *
50
- * @param scalar $uid The unique registration ID.
51
- * @param array $dimensions The dimensions, annodated with 'height' and 'width'.
52
  */
53
- public function register_image_dimension( $uid, array $dimensions ) {
54
- $this->image_dimensions += [ $uid => $dimensions ];
 
55
  }
56
 
57
  /**
58
- * Tests and registers image dimensions from custom input.
59
- * This is meant to sanitize the JavaScript handler for custom input sources on the front-end.
60
- *
61
- * @since 3.1.0
62
- * @internal
63
- * @uses $this->register_image_dimension()
64
- *
65
- * @param int $src_id The image source ID.
66
- * @param string $src The known image source.
67
- * @param scalar $uid The unique registration ID.
 
 
 
 
 
 
 
68
  */
69
- protected function register_custom_image_dimensions( $src_id, $src, $uid ) {
70
-
71
- $_src = \wp_get_attachment_image_src( $src_id, 'full' );
72
-
73
- if ( is_array( $_src ) && count( $_src ) >= 3 ) {
74
- $i = $_src[0]; // Source URL
75
- $w = $_src[1]; // Width
76
- $h = $_src[2]; // Height
77
-
78
- $test_i = \esc_url_raw( $this->set_preferred_url_scheme( $i ), [ 'http', 'https' ] );
79
- $test_src = \esc_url_raw( $this->set_preferred_url_scheme( $src ), [ 'http', 'https' ] );
80
-
81
- if ( $test_i === $test_src )
82
- $this->register_image_dimension( $uid, [
83
- 'width' => $w,
84
- 'height' => $h,
85
- ] );
86
- }
87
- }
88
-
89
- /**
90
- * Returns image URL suitable for Schema items.
91
- *
92
- * These are images that are strictly assigned to the Post or Page.
93
- * Themes should compliment these. If not, then Open Graph should at least
94
- * compliment these.
95
- * If that's not even true, then I don't know what happens. But then you're
96
- * in a grey area... @TODO make images optional for Schema?
97
- *
98
- * @since 2.9.3
99
- * @since 3.2.2 No longer relies on the query.
100
- * @uses $this->get_social_image()
101
- * @staticvar array $images
102
- * @TODO exchange 2nd argument with $taxonomy.
103
- *
104
- * @TODO support Terms.
105
- *
106
- * @param int|string $id The page, post, product or term ID.
107
- * @param bool $singular Whether the ID is singular or archival.
108
- * @return string $url The Schema.org safe image.
109
- */
110
- public function get_schema_image( $id = 0, $singular = false ) {
111
-
112
- //= TODO remove this when term images are introduced.
113
- if ( ! $singular )
114
- return '';
115
-
116
- static $images = [];
117
-
118
- $id = (int) $id;
119
 
120
- if ( isset( $images[ $id ][ $singular ] ) )
121
- return $images[ $id ][ $singular ];
122
 
123
- if ( $singular ) {
124
- if ( $this->is_real_front_page_by_id( $id ) ) {
125
- $image_args = [
126
- 'post_id' => $id,
127
- 'skip_fallback' => true,
128
- 'escape' => false,
129
- ];
130
- } else {
131
- $image_args = [
132
- 'post_id' => $id,
133
- 'skip_fallback' => true,
134
- 'disallowed' => [
135
- 'homemeta',
136
- ],
137
- 'escape' => false,
138
- ];
139
- }
140
- $url = $this->get_social_image( $image_args, false );
141
  } else {
142
- //* Placeholder for when Terms get image uploads.
143
- $url = '';
 
 
144
  }
145
 
146
- /**
147
- * @since 2.7.0
148
- * TODO deprecate filter and exchange with a suiting name.
149
- * @param string $image The current image.
150
- * @param int $id The page, post, product or term ID.
151
- * @param bool $singular Whether the ID is singular.
152
- */
153
- $url = \apply_filters( 'the_seo_framework_ld_json_breadcrumb_image', $url, $id, $singular );
154
-
155
- return $images[ $id ][ $singular ] = \esc_url_raw( $url );
156
  }
157
 
158
  /**
159
- * Returns social image URL and sets $this->image_dimensions.
160
- *
161
- * @since 2.9.0
162
- * @since 3.0.6 Added attachment page compatibility.
163
- * @since 3.2.2 Now skips the singular meta images on archives.
164
- *
165
- * @todo listen to attached images within post.
166
- * @todo listen to archive images. -> set taxonomy argument.
167
- *
168
- * @param array $args The image arguments.
169
- * @param bool $set_og_dimension Whether to set open graph dimensions.
170
- * @return string The social image.
 
 
 
 
171
  */
172
- public function get_social_image( $args = [], $set_og_dimension = false ) {
173
-
174
- $args = $this->reparse_image_args( $args );
175
-
176
- //* 0. Image from argument.
177
- pre_0 : {
178
- if ( $image = $args['image'] )
179
- goto end;
180
- }
181
 
182
- //* Check if there are no disallowed arguments.
183
- $all_allowed = empty( $args['disallowed'] );
184
-
185
- //* 1. Fetch image from homepage SEO meta upload.
186
- if ( $all_allowed || false === in_array( 'homemeta', $args['disallowed'], true ) ) {
187
- if ( $image = $this->get_social_image_url_from_home_meta( $args['post_id'], true ) )
188
- goto end;
189
- }
190
-
191
- // FIXME: `! $this->is_archive()` = Patch for taxonomies taking incorrect images...
192
- if ( $args['post_id'] && ! $this->is_archive() ) {
193
- //* 2. Fetch image from SEO meta upload.
194
- if ( $all_allowed || false === in_array( 'postmeta', $args['disallowed'], true ) ) {
195
- if ( $image = $this->get_social_image_url_from_post_meta( $args['post_id'], true ) )
196
- goto end;
197
- }
198
-
199
- //* 2.5 Fetch image from fallback filter 0
200
- custom_0 : {
201
- /**
202
- * Use this to set a secondary custom image input.
203
- * @since 3.1.2
204
- * @param string $image The image URL.
205
- * @param int $post_id The post ID.
206
- */
207
- if ( $image = (string) \apply_filters( 'the_seo_framework_og_image_alt_custom', '', $args['post_id'] ) )
208
- goto end;
209
- }
210
-
211
- //* 3.a. Fetch image from featured.
212
- if ( $all_allowed || false === in_array( 'featured', $args['disallowed'], true ) ) {
213
- if ( $image = $this->get_social_image_url_from_post_thumbnail( $args['post_id'], $args, true ) )
214
- goto end;
215
- }
216
- //* 3.b. Fetch image from attachment page.
217
- if ( $all_allowed || false === in_array( 'attachment', $args['disallowed'], true ) ) {
218
- if ( $image = $this->get_social_image_url_from_attachment( $args['post_id'], $args, true ) )
219
- goto end;
220
- }
221
- }
222
-
223
- if ( $args['skip_fallback'] )
224
- goto end;
225
-
226
- //* 3.5 Fetch image from fallback filter 0
227
- fallback_0 : {
228
- /**
229
- * This runs before the SEO settings' fallback image call.
230
- * @since 3.1.2
231
- * @param string $image The image URL.
232
- * @param int $post_id The post ID.
233
- */
234
- if ( $image = (string) \apply_filters( 'the_seo_framework_og_image_fallback', '', $args['post_id'] ) )
235
- goto end;
236
- }
237
-
238
- //* 4. Fetch image from SEO settings
239
- if ( $all_allowed || false === in_array( 'option', $args['disallowed'], true ) ) {
240
- if ( $image = $this->get_social_image_url_from_seo_settings( true ) )
241
- goto end;
242
- }
243
-
244
- //* 5. Fetch image from fallback filter 1
245
- fallback_1 : {
246
- /**
247
- * NOTE: filter is slightly mislabeled.
248
- * Runs after the image from the SEO settings is fetched.
249
- * @since 2.5.2
250
- * @param string $image The image URL.
251
- * @param int $post_id The post ID.
252
- */
253
- if ( $image = (string) \apply_filters( 'the_seo_framework_og_image_after_featured', '', $args['post_id'] ) )
254
- goto end;
255
- }
256
-
257
- //* 6. Fallback: Get header image if exists
258
- if ( ( $all_allowed || false === in_array( 'header', $args['disallowed'], true ) ) && \current_theme_supports( 'custom-header', 'default-image' ) ) {
259
- if ( $image = $this->get_header_image( true ) )
260
- goto end;
261
- }
262
-
263
- //* 7. Fetch image from fallback filter 2
264
- /**
265
- * Runs after theme images are fetched.
266
- * @since 2.5.2
267
- * @param string $image The image URL.
268
- * @param int $post_id The post ID.
269
- */
270
- fallback_2 : {
271
- if ( $image = (string) \apply_filters( 'the_seo_framework_og_image_after_header', '', $args['post_id'] ) )
272
- goto end;
273
- }
274
-
275
- //* 8. Get the WP 4.5 Site Logo
276
- if ( ( $all_allowed || false === in_array( 'logo', $args['disallowed'], true ) ) && $this->can_use_logo() ) {
277
- if ( $image = $this->get_site_logo( true ) )
278
- goto end;
279
- }
280
-
281
- //* 9. Get the WP 4.3 Site Icon
282
- if ( $all_allowed || false === in_array( 'icon', $args['disallowed'], true ) ) {
283
- if ( $image = $this->get_site_icon( 'full', true ) )
284
- goto end;
285
  }
286
 
287
- end :;
288
-
289
- if ( $args['escape'] && $image )
290
- $image = \esc_url( $image );
291
-
292
- return (string) $image;
293
  }
294
 
295
  /**
296
- * Parse and sanitize image args.
297
- *
298
- * @since 2.5.0
299
- * @since 2.9.0 : 1. Removed 'attr' index, it was unused.
300
- * 2. Added 'skip_fallback' option.
301
- * 3. Added 'escape' option.
302
- *
303
- * The image set in the filter will always be used as fallback
304
- *
305
- * @param array $args required The passed arguments.
306
- * @param array $defaults The default arguments.
307
- * @param bool $get_defaults Return the default arguments. Ignoring $args.
308
- * @return array $args parsed args.
 
 
 
 
309
  */
310
- public function parse_image_args( $args = [], $defaults = [], $get_defaults = false ) {
311
-
312
- //* Passing back the defaults reduces the memory usage.
313
- if ( empty( $defaults ) ) {
314
- $defaults = [
315
- 'post_id' => $this->get_the_real_ID(),
316
- 'image' => '',
317
- 'size' => 'full',
318
- 'icon' => false,
319
- 'skip_fallback' => false,
320
- 'disallowed' => [],
321
- 'escape' => true,
322
- ];
323
 
324
- /**
325
- * @since 2.0.1
326
- * @param array $defaults The image defaults: {
327
- * @param string $image The image url
328
- * @param mixed $size The image size
329
- * @param bool $icon Fetch Image icon
330
- * @param bool 'skip_fallback' Whether to skip fallback images.
331
- * @param array $disallowed Disallowed image types : Allowed values {
332
- * array (
333
- * 'homemeta',
334
- * 'postmeta',
335
- * 'featured',
336
- * 'attachment',
337
- * 'option',
338
- * 'header',
339
- * 'logo',
340
- * 'icon',
341
- * )
342
- * }
343
- * @param bool 'escape' Whether to escape output.
344
- * }
345
- * @param array $args The input args.
346
- */
347
- $defaults = (array) \apply_filters( 'the_seo_framework_og_image_args', $defaults, $args );
348
  }
349
 
350
- //* Return early if it's only a default args request.
351
- if ( $get_defaults )
352
- return $defaults;
353
-
354
- //* Array merge doesn't support sanitation. We're simply type casting here.
355
- // phpcs:disable WordPress.WhiteSpace.OperatorSpacing.SpacingBefore -- precision alignment OK.
356
- $args['post_id'] = isset( $args['post_id'] ) ? (int) $args['post_id'] : $defaults['post_id'];
357
- $args['image'] = isset( $args['image'] ) ? (string) $args['image'] : $defaults['image'];
358
- $args['size'] = isset( $args['size'] ) ? $args['size'] : $defaults['size']; // Mixed.
359
- $args['icon'] = isset( $args['icon'] ) ? (bool) $args['icon'] : $defaults['icon'];
360
- $args['skip_fallback'] = isset( $args['skip_fallback'] ) ? (bool) $args['skip_fallback'] : $defaults['skip_fallback'];
361
- $args['disallowed'] = isset( $args['disallowed'] ) ? (array) $args['disallowed'] : $defaults['disallowed'];
362
- $args['escape'] = isset( $args['escape'] ) ? (bool) $args['escape'] : $defaults['escape'];
363
- // phpcs:enable WordPress.WhiteSpace.OperatorSpacing.SpacingBefore
364
-
365
- return $args;
366
  }
367
 
368
  /**
369
- * Reparses image args.
370
  *
371
- * @since 2.6.6
372
- * @since 2.9.2 Now passes args to filter.
373
  *
374
- * @param array $args required The passed arguments.
375
- * @return array $args parsed args.
 
 
 
 
 
376
  */
377
- public function reparse_image_args( $args = [] ) {
378
-
379
- $default_args = $this->parse_image_args( $args, '', true );
380
 
381
- if ( empty( $args ) ) {
382
- $args = $default_args;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  } else {
384
- $args = $this->parse_image_args( $args, $default_args );
 
 
 
385
  }
386
 
387
- return $args;
388
- }
389
-
390
- /**
391
- * Returns unescaped HomePage settings image URL from post ID input.
392
- *
393
- * @since 2.9.0
394
- * @since 2.9.4 Now converts URL scheme.
395
- *
396
- * @param int $id The post ID.
397
- * @param bool $set_og_dimensions Whether to set Open Graph and Twitter dimensions.
398
- * @return string The unescaped HomePage social image URL.
399
- */
400
- public function get_social_image_url_from_home_meta( $id = 0, $set_og_dimensions = false ) {
401
-
402
- //* Don't output if not on the front page ...FIXME... by query.
403
- if ( false === $this->is_front_page_by_id( $id ) )
404
- return '';
405
-
406
- $src = $this->get_option( 'homepage_social_image_url' );
407
-
408
- if ( ! $src )
409
- return '';
410
-
411
- //* Calculate image sizes.
412
- if ( $set_og_dimensions && $img_id = $this->get_option( 'homepage_social_image_id' ) )
413
- $this->register_custom_image_dimensions( $img_id, $src, $id );
414
-
415
- if ( $src && $this->matches_this_domain( $src ) )
416
- $src = $this->set_preferred_url_scheme( $src );
417
-
418
- return $src;
419
- }
420
-
421
- /**
422
- * Returns unescaped Post settings image URL from post ID input.
423
- *
424
- * @since 2.8.0
425
- * @since 2.9.0 1. The second parameter now works.
426
- * 2. Fallback image ID has been removed.
427
- * @since 2.9.4 Now converts URL scheme.
428
- *
429
- * @param int $id The post ID. Required.
430
- * @param bool $set_og_dimensions Whether to set Open Graph and Twitter dimensions.
431
- * @return string The unescaped social image URL.
432
- */
433
- public function get_social_image_url_from_post_meta( $id, $set_og_dimensions = false ) {
434
-
435
- $src = $id ? $this->get_custom_field( '_social_image_url', $id ) : '';
436
-
437
- if ( ! $src )
438
- return '';
439
-
440
- //* Calculate image sizes.
441
- if ( $set_og_dimensions && $img_id = $this->get_custom_field( '_social_image_id', $id ) )
442
- $this->register_custom_image_dimensions( $img_id, $src, $id );
443
-
444
- if ( $src && $this->matches_this_domain( $src ) )
445
- $src = $this->set_preferred_url_scheme( $src );
446
 
447
- return $src;
448
  }
449
 
450
  /**
451
- * Returns unescaped URL from options input.
452
- *
453
- * @since 2.8.2
454
- * @since 2.9.4 1: Now converts URL scheme.
455
- * 2: $set_og_dimensions now works.
456
- *
457
- * @param bool $set_og_dimensions Whether to set open graph and twitter dimensions.
458
- * @return string The unescaped social image fallback URL.
 
 
 
 
459
  */
460
- public function get_social_image_url_from_seo_settings( $set_og_dimensions = false ) {
461
-
462
- $src = $this->get_option( 'social_image_fb_url' );
463
-
464
- if ( ! $src )
465
- return '';
466
 
467
- //* Calculate image sizes.
468
- if ( $set_og_dimensions && $img_id = $this->get_option( 'social_image_fb_id' ) )
469
- $this->register_custom_image_dimensions( $img_id, $src, $this->get_the_real_ID() );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
 
471
- if ( $src && $this->matches_this_domain( $src ) )
472
- $src = $this->set_preferred_url_scheme( $src );
 
 
 
 
 
 
473
 
474
- return $src;
475
  }
476
 
477
  /**
478
- * Fetches image from post thumbnail.
479
- *
480
- * Resizes the image between 4096px if bigger. Then it saves the image and
481
- * Keeps dimensions relative.
482
- *
483
- * @since 2.9.0
484
- * @since 2.9.3 Now supports 4K.
485
- * @since 2.9.4 Now converts URL scheme.
486
- *
487
- * @param int $id The post ID. Required.
488
- * @param array $args The image args.
489
- * @param bool $set_og_dimensions Whether to set Open Graph image dimensions.
490
- * @return string The social image URL.
 
 
 
 
491
  */
492
- public function get_social_image_url_from_post_thumbnail( $id, $args = [], $set_og_dimensions = false ) {
493
-
494
- $image_id = $id ? \get_post_thumbnail_id( $id ) : '';
495
-
496
- if ( ! $image_id )
497
- return '';
498
-
499
- $args = $this->reparse_image_args( $args );
500
- $args['get_the_real_ID'] = true;
501
-
502
- $src = $this->parse_og_image( $image_id, $args, $set_og_dimensions );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
 
504
- if ( $src && $this->matches_this_domain( $src ) )
505
- $src = $this->set_preferred_url_scheme( $src );
 
 
 
 
 
 
 
 
506
 
507
- return $src;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
  }
509
 
510
  /**
511
- * Returns the social image URL from an attachment page.
512
- *
513
- * @since 3.0.6
514
- *
515
- * @param int $id The post ID. Required.
516
- * @param array $args The image args.
517
- * @param bool $set_og_dimensions Whether to set Open Graph image dimensions.
518
- * @return string The attachment URL.
 
 
 
 
 
 
 
519
  */
520
- public function get_social_image_url_from_attachment( $id, $args = [], $set_og_dimensions = false ) {
521
-
522
- if ( ! \wp_attachment_is_image( $id ) )
523
- return '';
524
-
525
- $args = $this->reparse_image_args( $args );
526
- $args['get_the_real_ID'] = true;
527
 
528
- $src = $this->parse_og_image( $id, $args, $set_og_dimensions );
 
529
 
530
- if ( $src && $this->matches_this_domain( $src ) )
531
- $src = $this->set_preferred_url_scheme( $src );
 
532
 
533
- return $src;
534
  }
535
 
536
  /**
537
- * Fetches images id's from WooCommerce gallery
538
- *
539
- * @since 2.5.0
540
- * @staticvar array $ids The image IDs
541
- *
542
- * @param array $args Image arguments.
543
- * @return array The image URL's.
 
 
 
 
 
 
 
 
 
544
  */
545
- public function get_image_from_woocommerce_gallery() {
546
-
547
- static $ids = null;
548
 
549
- if ( isset( $ids ) )
550
- return $ids;
551
 
552
- $attachment_ids = '';
553
-
554
- $post_id = $this->get_the_real_ID();
555
-
556
- if ( \metadata_exists( 'post', $post_id, '_product_image_gallery' ) ) {
557
- $product_image_gallery = \get_post_meta( $post_id, '_product_image_gallery', true );
558
-
559
- $attachment_ids = array_map( 'absint', array_filter( explode( ',', $product_image_gallery ) ) );
560
  }
561
 
562
- return $ids = $attachment_ids;
563
  }
564
 
565
  /**
566
- * Parses OG image to correct size.
567
- *
568
- * @since 2.5.0
569
- * @since 2.8.0 : 1. Removed staticvar.
570
- * 2. Now adds ID call to OG image called listener.
571
- * @since 2.9.0 : Added $set_og_dimension parameter
572
- * @since 2.9.3 : 4k baby.
573
- * @since 3.0.0 : Now sets preferred canonical URL scheme.
574
- *
575
- * @todo create formula to fetch transient.
576
- * @todo See \TSF_Extension_Manager\Extension\Articles\Front::make_amp_logo() for a better alternative?
577
- * @priority high 2.7.0
578
- * @priority lowered with 4K @ 2.9.3
579
- *
580
- * @param int $id The attachment ID.
581
- * @param array $args The image args
582
- * @param bool $set_og_dimensions Whether to set OG dimensions.
583
- * @return string Parsed image url or empty if already called
584
  */
585
- public function parse_og_image( $id, $args = [], $set_og_dimensions = false ) {
586
-
587
- //* Don't do anything if $id isn't given.
588
- if ( empty( $id ) )
589
- return;
590
-
591
- if ( empty( $args ) )
592
- $args = $this->reparse_image_args( $args );
593
-
594
- $src = \wp_get_attachment_image_src( $id, $args['size'], $args['icon'] );
595
-
596
- $i = $src[0]; // Source URL
597
- $w = $src[1]; // Width
598
- $h = $src[2]; // Height
599
-
600
- //* @TODO add filter that can lower it?
601
- $_size = 4096;
602
 
603
- //* Preferred 4096px, resize it
604
- if ( $w > $_size || $h > $_size ) :
605
 
606
- if ( $w === $h ) {
607
- //* Square
608
- $w = $_size;
609
- $h = $_size;
610
- } elseif ( $w > $h ) {
611
- //* Landscape, set $w to 4096.
612
- $h = $this->proportionate_dimensions( $h, $w, $w = $_size );
613
- } elseif ( $h > $w ) {
614
- //* Portrait, set $h to 4096.
615
- $w = $this->proportionate_dimensions( $w, $h, $h = $_size );
616
- }
617
-
618
- //* Get path of image and load it into the wp_get_image_editor
619
- $i_file_path = \get_attached_file( $id );
620
- $i_file_ext = pathinfo( $i_file_path, PATHINFO_EXTENSION );
621
-
622
- if ( $i_file_ext ) {
623
- $i_file_dir_name = pathinfo( $i_file_path, PATHINFO_DIRNAME );
624
- //* Add trailing slash.
625
- $i_file_dir_name = '/' === substr( $i_file_dir_name, -1 ) ? $i_file_dir_name : $i_file_dir_name . '/';
626
-
627
- $i_file_file_name = pathinfo( $i_file_path, PATHINFO_FILENAME );
628
-
629
- //* Yes I know, I should use generate_filename(), but it's slower.
630
- //* Will look at that later. This is already 100 lines of correctly working code.
631
- $new_image_dirfile = $i_file_dir_name . $i_file_file_name . '-' . $w . 'x' . $h . '.' . $i_file_ext;
632
-
633
- //* Generate image URL.
634
- $upload_dir = \wp_upload_dir();
635
- $upload_url = $upload_dir['baseurl'];
636
- $upload_basedir = $upload_dir['basedir'];
637
-
638
- //* We've got our image path.
639
- $i = str_ireplace( $upload_basedir, '', $new_image_dirfile );
640
- $i = $upload_url . $i;
641
-
642
- // Generate file if it doesn't exists yet.
643
- if ( ! file_exists( $new_image_dirfile ) ) {
644
-
645
- $image_editor = \wp_get_image_editor( $i_file_path );
646
-
647
- if ( ! \is_wp_error( $image_editor ) ) {
648
- $image_editor->resize( $w, $h, false );
649
- $image_editor->set_quality( 82 ); // Let's save some bandwidth, Facebook compresses it even further anyway.
650
- $image_editor->save( $new_image_dirfile );
651
- } else {
652
- //* Image has failed to create.
653
- $i = '';
654
- }
655
- }
656
- }
657
- endif;
658
-
659
- if ( $set_og_dimensions ) {
660
- //* Whether to use the post ID (Post Thumbnail) or input ID (ID was known beforehand)
661
- $usage_id = ! empty( $args['get_the_real_ID'] ) ? $this->get_the_real_ID() : $id;
662
-
663
- $this->register_image_dimension( $usage_id, [
664
- 'width' => $w,
665
- 'height' => $h,
666
- ] );
667
- }
668
-
669
- if ( $i && $this->matches_this_domain( $i ) )
670
- $i = $this->set_preferred_url_scheme( $i );
671
-
672
- return $i;
673
  }
674
 
675
  /**
676
- * Fetches site icon brought in WordPress 4.3
677
  *
678
- * @since 2.8.0
679
- * @since 3.0.0 : Now sets preferred canonical URL scheme.
680
  *
681
- * @param string|int $size The icon size, accepts 'full' and pixel values.
682
- * @param bool $set_og_dimensions Whether to set size for OG image. Always falls back to the current post ID.
683
- * @return string URL site icon, not escaped.
 
 
 
 
684
  */
685
- public function get_site_icon( $size = 'full', $set_og_dimensions = false ) {
686
 
687
- $icon = '';
688
 
689
- if ( 'full' === $size ) {
690
- $site_icon_id = \get_option( 'site_icon' );
 
 
691
 
692
- if ( $site_icon_id ) {
693
- $_src = \wp_get_attachment_image_src( $site_icon_id, $size );
694
- if ( is_array( $_src ) && count( $_src ) >= 3 ) {
695
 
696
- $icon = $_src[0];
 
697
 
698
- if ( $set_og_dimensions ) {
699
- $this->register_image_dimension( $this->get_the_real_ID(), [
700
- 'width' => $_src[1],
701
- 'height' => $_src[2],
702
- ] );
703
- }
704
- }
705
  }
706
- } elseif ( is_int( $size ) ) {
707
- //* Above 512 defaults to full. Loop back.
708
- if ( $size > 512 )
709
- return $this->get_site_icon( 'full', $set_og_dimensions );
710
-
711
- //* Also applies (MultiSite) filters.
712
- $icon = \get_site_icon_url( $size );
713
  }
714
 
715
- if ( $icon && $this->matches_this_domain( $icon ) )
716
- $icon = $this->set_preferred_url_scheme( $icon );
717
-
718
- return $icon;
719
  }
720
 
721
  /**
722
- * Fetches site logo brought in WordPress 4.5
723
  *
724
- * @since 2.8.0
725
- * @since 3.0.0 Now sets preferred canonical URL scheme.
726
- * @since 3.1.2 Now returns empty when it's deemed too small, and OG images are set.
727
  *
728
- * @param bool $set_og_dimensions Whether to set size for OG image. Always falls back to the current post ID.
729
- * @return string URL site logo, not escaped.
730
  */
731
- public function get_site_logo( $set_og_dimensions = false ) {
732
-
733
- if ( false === $this->can_use_logo() )
734
- return '';
735
-
736
- $logo = '';
737
-
738
- $site_logo_id = \get_theme_mod( 'custom_logo' );
739
-
740
- if ( $site_logo_id ) {
741
- $_src = \wp_get_attachment_image_src( $site_logo_id, 'full' );
742
- if ( is_array( $_src ) && count( $_src ) >= 3 ) {
743
- $logo = $_src[0];
744
-
745
- if ( $set_og_dimensions ) {
746
- $w = $_src[1];
747
- $h = $_src[2];
748
-
749
- if ( $w < 200 || $h < 200 )
750
- return '';
751
-
752
- $this->register_image_dimension( $this->get_the_real_ID(), [
753
- 'width' => $w,
754
- 'height' => $h,
755
- ] );
756
- }
757
- }
758
- }
759
-
760
- if ( $logo && $this->matches_this_domain( $logo ) )
761
- $logo = $this->set_preferred_url_scheme( $logo );
762
-
763
- return $logo;
764
  }
765
 
766
  /**
767
- * Returns header image URL.
768
- * Also sets image dimensions. Falls back to current post ID for index.
769
  *
770
- * @since 2.7.0
771
- * @since 3.0.0 Now sets preferred canonical URL scheme.
772
  *
773
- * @param bool $set_og_dimensions Whether to set size for OG image. Always falls back to the current post ID.
774
- * @return string The header image URL, not escaped.
 
775
  */
776
- public function get_header_image( $set_og_dimensions = false ) {
777
 
778
- $image = \get_header_image();
 
 
779
 
780
- if ( $set_og_dimensions && $image ) {
 
 
781
 
782
- $w = (int) \get_theme_support( 'custom-header', 'width' );
783
- $h = (int) \get_theme_support( 'custom-header', 'height' );
784
-
785
- if ( $w && $h ) {
786
- $this->register_image_dimension( $this->get_the_real_ID(), [
787
- 'width' => $w,
788
- 'height' => $h,
789
- ] );
790
  }
791
  }
792
 
793
- if ( $image && $this->matches_this_domain( $image ) )
794
- $image = $this->set_preferred_url_scheme( $image );
795
-
796
- return $image;
797
  }
798
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Generate_Image
4
+ * @subpackage The_SEO_Framework\Getters\Image
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
35
  class Generate_Image extends Generate_Url {
36
 
37
  /**
38
+ * Returns the image details from cache.
39
+ * Only to be used within the loop, uses default parameters, inlucing the 'social' context.
40
+ *
41
+ * @since 4.0.0
42
+ * @staticvar array $cache
43
+ *
44
+ * @return array The image details array, sequential: int => {
45
+ * string url: The image URL,
46
+ * int id: The image ID,
47
+ * int width: The image width in pixels,
48
+ * int height: The image height in pixels,
49
+ * string alt: The image alt tag,
50
+ * }
 
 
 
51
  */
52
+ public function get_image_details_from_cache() {
53
+ static $cache;
54
+ return isset( $cache ) ? $cache : $cache = $this->get_image_details();
55
  }
56
 
57
  /**
58
+ * Returns image details.
59
+ *
60
+ * @since 4.0.0
61
+ *
62
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
63
+ * Leave null to autodetermine query.
64
+ * @param bool $single Whether to fetch one image, or multiple.
65
+ * @param string $context The filter context. Default 'social'.
66
+ * @param bool $clean Whether to clean the image, like stripping duplicates and erroneous items.
67
+ * It's best to leave this enabled, unless you're merging the calls, and clean up yourself.
68
+ * @return array The image details array, sequential: int => {
69
+ * string url: The image URL,
70
+ * int id: The image ID,
71
+ * int width: The image width in pixels,
72
+ * int height: The image height in pixels,
73
+ * string alt: The image alt tag,
74
+ * }
75
  */
76
+ public function get_image_details( $args = null, $single = false, $context = 'social', $clean = true ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
+ if ( $single ) {
79
+ $details = $this->get_custom_field_image_details( $args, $single, false );
80
 
81
+ if ( empty( $details[0]['url'] ) )
82
+ $details = $this->get_generated_image_details( $args, $single, $context, false );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  } else {
84
+ $details = array_merge(
85
+ $this->get_custom_field_image_details( $args, $single, false ),
86
+ $this->get_generated_image_details( $args, $single, $context, false )
87
+ );
88
  }
89
 
90
+ return $clean ? $this->s_image_details( $details ) : $details;
 
 
 
 
 
 
 
 
 
91
  }
92
 
93
  /**
94
+ * Returns single custom field image details.
95
+ *
96
+ * @since 4.0.0
97
+ *
98
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
99
+ * Leave null to autodetermine query.
100
+ * @param bool $single Whether to fetch one image, or multiple. Unused, reserved.
101
+ * @param bool $clean Whether to clean the image, like stripping duplicates and erroneous items.
102
+ * It's best to leave this enabled, unless you're merging the calls, and clean up yourself.
103
+ * @return array The image details array, sequential: int => {
104
+ * string url: The image URL,
105
+ * int id: The image ID,
106
+ * int width: The image width in pixels,
107
+ * int height: The image height in pixels,
108
+ * string alt: The image alt tag,
109
+ * }
110
  */
111
+ public function get_custom_field_image_details( $args = null, $single = false, $clean = true ) {
 
 
 
 
 
 
 
 
112
 
113
+ if ( null === $args ) {
114
+ $details = $this->get_custom_field_image_details_from_query();
115
+ } else {
116
+ $this->fix_generation_args( $args );
117
+ $details = $this->get_custom_field_image_details_from_args( $args );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  }
119
 
120
+ return $clean ? $this->s_image_details( $details ) : $details;
 
 
 
 
 
121
  }
122
 
123
  /**
124
+ * Returns single or multiple generates image details.
125
+ *
126
+ * @since 4.0.0
127
+ *
128
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
129
+ * Leave null to autodetermine query.
130
+ * @param bool $single Whether to fetch one image, or multiple.
131
+ * @param string $context The filter context. Default 'social'.
132
+ * @param bool $clean Whether to clean the image, like stripping duplicates and erroneous items.
133
+ * It's best to leave this enabled, unless you're merging the calls, and clean up yourself.
134
+ * @return array The image details array, sequential: int => {
135
+ * string url: The image URL,
136
+ * int id: The image ID,
137
+ * int width: The image width in pixels,
138
+ * int height: The image height in pixels,
139
+ * string alt: The image alt tag,
140
+ * }
141
  */
142
+ public function get_generated_image_details( $args = null, $single = false, $context = 'social', $clean = true ) {
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
+ if ( null === $args ) {
145
+ $details = $this->generate_image_details( null, $single, $context );
146
+ } else {
147
+ $this->fix_generation_args( $args );
148
+ $details = $this->generate_image_details( $args, $single, $context );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  }
150
 
151
+ return $clean ? $this->s_image_details( $details ) : $details;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  }
153
 
154
  /**
155
+ * Returns single custom field image details from query.
156
  *
157
+ * @since 4.0.0
 
158
  *
159
+ * @return array The image details array, sequential: int => {
160
+ * string url: The image URL,
161
+ * int id: The image ID,
162
+ * int width: The image width in pixels,
163
+ * int height: The image height in pixels,
164
+ * string alt: The image alt tag,
165
+ * }
166
  */
167
+ protected function get_custom_field_image_details_from_query() {
 
 
168
 
169
+ if ( $this->is_real_front_page() ) {
170
+ if ( $this->is_static_frontpage() ) {
171
+ $details = [
172
+ 'url' => $this->get_option( 'homepage_social_image_url' ),
173
+ 'id' => $this->get_option( 'homepage_social_image_id' ),
174
+ ];
175
+ if ( ! $details['url'] ) {
176
+ $details = [
177
+ 'url' => $this->get_post_meta_item( '_social_image_url' ),
178
+ 'id' => $this->get_post_meta_item( '_social_image_id' ),
179
+ ];
180
+ }
181
+ } else {
182
+ $details = [
183
+ 'url' => $this->get_option( 'homepage_social_image_url' ),
184
+ 'id' => $this->get_option( 'homepage_social_image_id' ),
185
+ ];
186
+ }
187
+ } elseif ( $this->is_singular() ) {
188
+ $details = [
189
+ 'url' => $this->get_post_meta_item( '_social_image_url' ),
190
+ 'id' => $this->get_post_meta_item( '_social_image_id' ),
191
+ ];
192
+ } elseif ( $this->is_term_meta_capable() ) {
193
+ $details = [
194
+ 'url' => $this->get_term_meta_item( 'social_image_url' ),
195
+ 'id' => $this->get_term_meta_item( 'social_image_id' ),
196
+ ];
197
  } else {
198
+ $details = [
199
+ 'url' => '',
200
+ 'id' => 0,
201
+ ];
202
  }
203
 
204
+ if ( $details['url'] ) {
205
+ $details = $this->merge_extra_image_details( $details, 'full' );
206
+ } else {
207
+ $details = [
208
+ 'url' => '',
209
+ 'id' => 0,
210
+ ];
211
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
+ return [ $details ];
214
  }
215
 
216
  /**
217
+ * Returns single custom field image details from arguments.
218
+ *
219
+ * @since 4.0.0
220
+ *
221
+ * @param array $args The query arguments. Must have 'id' and 'taxonomy'.
222
+ * @return array The image details array, sequential: int => {
223
+ * string url: The image URL,
224
+ * int id: The image ID,
225
+ * int width: The image width in pixels,
226
+ * int height: The image height in pixels,
227
+ * string alt: The image alt tag,
228
+ * }
229
  */
230
+ protected function get_custom_field_image_details_from_args( $args ) {
 
 
 
 
 
231
 
232
+ if ( $args['taxonomy'] ) {
233
+ $details = [
234
+ 'url' => $this->get_term_meta_item( 'social_image_url', $args['id'] ),
235
+ 'id' => $this->get_term_meta_item( 'social_image_id', $args['id'] ),
236
+ ];
237
+ } else {
238
+ if ( $this->is_static_frontpage( $args['id'] ) ) {
239
+ $details = [
240
+ 'url' => $this->get_option( 'homepage_social_image_url' ),
241
+ 'id' => $this->get_option( 'homepage_social_image_id' ),
242
+ ];
243
+ if ( ! $details['url'] ) {
244
+ $details = [
245
+ 'url' => $this->get_post_meta_item( '_social_image_url', $args['id'] ),
246
+ 'id' => $this->get_post_meta_item( '_social_image_id', $args['id'] ),
247
+ ];
248
+ }
249
+ } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
250
+ $details = [
251
+ 'url' => $this->get_option( 'homepage_social_image_url' ),
252
+ 'id' => $this->get_option( 'homepage_social_image_id' ),
253
+ ];
254
+ } else {
255
+ $details = [
256
+ 'url' => $this->get_post_meta_item( '_social_image_url', $args['id'] ),
257
+ 'id' => $this->get_post_meta_item( '_social_image_id', $args['id'] ),
258
+ ];
259
+ }
260
+ }
261
 
262
+ if ( $details['url'] ) {
263
+ $details = $this->merge_extra_image_details( $details, 'full' );
264
+ } else {
265
+ $details = [
266
+ 'url' => '',
267
+ 'id' => 0,
268
+ ];
269
+ }
270
 
271
+ return [ $details ];
272
  }
273
 
274
  /**
275
+ * Returns image generation parameters.
276
+ *
277
+ * @since 4.0.0
278
+ *
279
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
280
+ * Leave null to autodetermine query.
281
+ * @param string $context The filter context. Default 'social'.
282
+ * May be (for example) 'breadcrumb' or 'article' for structured data.
283
+ * @return array The image generation parameters, associative: {
284
+ * string size: The image size,
285
+ * boolean multi: Whether multiple images may be returned,
286
+ * array cbs: An array of image generation callbacks, in order of most important to least.
287
+ * When 'multi' (or $single input) parameter is "false", it will use the first found.
288
+ * array fallback: An array of image generaiton callbacks, in order of most important to least,
289
+ * Only one image is obtained from the fallback, and only if the regular cbs don't
290
+ * return any image.
291
+ * }
292
  */
293
+ public function get_image_generation_params( $args = null, $context = 'social' ) {
294
+
295
+ if ( null !== $args )
296
+ $this->fix_generation_args( $args );
297
+
298
+ $builder = Builders\Images::class;
299
+
300
+ if ( null === $args ) {
301
+ if ( $this->is_singular() ) {
302
+ if ( $this->is_attachment() ) {
303
+ $cbs = [
304
+ 'attachment' => "$builder::get_attachment_image_details",
305
+ ];
306
+ } else {
307
+ $cbs = [
308
+ 'featured' => "$builder::get_featured_image_details",
309
+ 'content' => "$builder::get_content_image_details",
310
+ ];
311
+ }
312
+ } elseif ( $this->is_term_meta_capable() ) {
313
+ $cbs = [];
314
+ } else {
315
+ $cbs = [];
316
+ }
317
+ } else {
318
+ if ( $args['taxonomy'] ) {
319
+ $cbs = [];
320
+ } else {
321
+ if ( \wp_attachment_is_image( $args['id'] ) ) {
322
+ $cbs = [
323
+ 'attachment' => "$builder::get_attachment_image_details",
324
+ ];
325
+ } else {
326
+ $cbs = [
327
+ 'featured' => "$builder::get_featured_image_details",
328
+ 'content' => "$builder::get_content_image_details",
329
+ ];
330
+ }
331
+ }
332
+ }
333
 
334
+ if ( 'social' === $context ) {
335
+ $fallback = [
336
+ 'settings' => "$builder::get_fallback_image_details",
337
+ 'header' => "$builder::get_theme_header_image_details",
338
+ 'logo' => "$builder::get_site_logo_image_details",
339
+ 'icon' => "$builder::get_site_icon_image_details",
340
+ ];
341
+ } else {
342
+ $fallback = [];
343
+ }
344
 
345
+ /**
346
+ * @since 4.0.0
347
+ * @param array $params : [
348
+ * string size: The image size to use.
349
+ * boolean multi: Whether to allow multiple images to be returned.
350
+ * array cbs: The callbacks to parse. Ideally be generators, so we can halt remotely.
351
+ * array fallback: The callbacks to parse. Ideally be generators, so we can halt remotely.
352
+ * ];
353
+ * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
354
+ * Is null when query is autodetermined.
355
+ * @param string $context The filter context. Default 'social'.
356
+ * May be (for example) 'breadcrumb' or 'article' for structured data.
357
+ */
358
+ return \apply_filters_ref_array(
359
+ 'the_seo_framework_image_generation_params',
360
+ [
361
+ [
362
+ 'size' => 'full',
363
+ 'multi' => true,
364
+ 'cbs' => $cbs,
365
+ 'fallback' => $fallback,
366
+ ],
367
+ $args,
368
+ $context,
369
+ ]
370
+ );
371
  }
372
 
373
  /**
374
+ * Generates image details.
375
+ *
376
+ * @since 4.0.0
377
+ *
378
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
379
+ * Leave null to autodetermine query.
380
+ * @param bool $single Whether to fetch one image, or multiple.
381
+ * @param string $context The context of the image generation, albeit 'social', 'schema', etc.
382
+ * @return array The image details array, sequential: int => {
383
+ * string url: The image URL,
384
+ * int id: The image ID,
385
+ * int width: The image width in pixels,
386
+ * int height: The image height in pixels,
387
+ * string alt: The image alt tag,
388
+ * }
389
  */
390
+ protected function generate_image_details( $args, $single = true, $context = 'social' ) {
 
 
 
 
 
 
391
 
392
+ $params = $this->get_image_generation_params( $args, $context );
393
+ $single = $single || ! $params['multi'];
394
 
395
+ // TODO s_image_details() here? The cbs may be discarded, and then we won't obtain any fallbacks...
396
+ $details = $this->process_image_cbs( $params['cbs'], $args, $params['size'], $single )
397
+ ?: $this->process_image_cbs( $params['fallback'], $args, $params['size'], true );
398
 
399
+ return $details;
400
  }
401
 
402
  /**
403
+ * Processes image detail callbacks.
404
+ *
405
+ * @since 4.0.0
406
+ *
407
+ * @param array $cbs The callbacks to parse. Ideally be generators, so we can halt early.
408
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
409
+ * Leave null to autodetermine query.
410
+ * @param sring $size The image size to use.
411
+ * @param bool $single Whether to fetch one image, or multiple.
412
+ * @return array The image details array, sequential: int => {
413
+ * string url: The image URL,
414
+ * int id: The image ID,
415
+ * int width: The image width in pixels,
416
+ * int height: The image height in pixels,
417
+ * string alt: The image alt tag,
418
+ * }
419
  */
420
+ protected function process_image_cbs( $cbs, $args, $size, $single ) {
 
 
421
 
422
+ $items = [];
423
+ $i = 0;
424
 
425
+ foreach ( $cbs as $cb ) {
426
+ foreach ( call_user_func_array( $cb, [ $args, $size ] ) as $details ) {
427
+ if ( $details['url'] && $this->s_url_query( $details['url'] ) ) {
428
+ $items[ $i++ ] = $this->merge_extra_image_details( $details, $size );
429
+ if ( $single ) break 2;
430
+ }
431
+ }
 
432
  }
433
 
434
+ return $items;
435
  }
436
 
437
  /**
438
+ * Adds image dimension and alt parameters to the input details, if any.
439
+ *
440
+ * @since 4.0.0
441
+ *
442
+ * @param array $details The image details array, associative: {
443
+ * string url: The image URL,
444
+ * int id: The image ID,
445
+ * }
446
+ * @param string $size The size of the image used.
447
+ * @return array The image details array, associative: {
448
+ * string url: The image URL,
449
+ * int id: The image ID,
450
+ * int width: The image width in pixels,
451
+ * int height: The image height in pixels,
452
+ * string alt: The image alt tag,
453
+ * }
 
 
454
  */
455
+ public function merge_extra_image_details( array $details, $size = 'full' ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
 
457
+ $details += $this->get_image_dimensions( $details['id'], $details['url'], $size );
458
+ $details += [ 'alt' => $this->get_image_alt_tag( $details['id'] ) ];
459
 
460
+ return $details;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
  }
462
 
463
  /**
464
+ * Generates image dimensions.
465
  *
466
+ * @since 4.0.0
 
467
  *
468
+ * @param int $src_id The source ID of the image.
469
+ * @param string $url The source URL of the image. Ideally related to the $src_id.
470
+ * @param string $size The size of the image used.
471
+ * @return array The image dimensions, associative: {
472
+ * int width: The image width in pixels,
473
+ * int height: The image height in pixels,
474
+ * }
475
  */
476
+ public function get_image_dimensions( $src_id, $url, $size ) {
477
 
478
+ $image = \wp_get_attachment_image_src( $src_id, $size );
479
 
480
+ $dimensions = [
481
+ 'width' => 0,
482
+ 'height' => 0,
483
+ ];
484
 
485
+ if ( $image ) {
486
+ list( $src, $width, $height ) = $image;
 
487
 
488
+ $test_src = \esc_url_raw( $this->set_url_scheme( $src, 'https' ), [ 'https', 'http' ] );
489
+ $test_url = \esc_url_raw( $this->set_url_scheme( $url, 'https' ), [ 'https', 'http' ] );
490
 
491
+ if ( $test_src === $test_url ) {
492
+ $dimensions = [
493
+ 'width' => $width,
494
+ 'height' => $height,
495
+ ];
 
 
496
  }
 
 
 
 
 
 
 
497
  }
498
 
499
+ return $dimensions;
 
 
 
500
  }
501
 
502
  /**
503
+ * Generates image dimensions.
504
  *
505
+ * @since 4.0.0
 
 
506
  *
507
+ * @param int $src_id The source ID of the image.
508
+ * @return string The image alt tag
509
  */
510
+ public function get_image_alt_tag( $src_id ) {
511
+ // phpcs:ignore, WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- Fix `wp_get_attachment_image()` first.
512
+ return $src_id ? trim( strip_tags( \get_post_meta( $src_id, '_wp_attachment_image_alt', true ) ) ) : '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
  }
514
 
515
  /**
516
+ * Returns the largest acceptable image size's details.
 
517
  *
518
+ * @since 4.0.2
 
519
  *
520
+ * @param int $id The image ID.
521
+ * @param int $max_size The largest acceptable size in pixels. Accounts for both width and height.
522
+ * @return false|array Returns an array (url, width, height, is_intermediate), or false, if no image is available.
523
  */
524
+ public function get_largest_acceptable_image_src( $id, $max_size = 4096 ) {
525
 
526
+ // Imply there's a correct ID set. When there's not, the loop won't run.
527
+ $meta = \wp_get_attachment_metadata( $id );
528
+ $sizes = ! empty( $meta['sizes'] ) && is_array( $meta['sizes'] ) ? $meta['sizes'] : [];
529
 
530
+ // law = largest accepted width.
531
+ $law = 0;
532
+ $size = '';
533
 
534
+ foreach ( $sizes as $_s => $_d ) {
535
+ if ( isset( $_d['width'], $_d['height'] ) ) {
536
+ if ( $_d['width'] <= $max_size && $_d['height'] <= $max_size && $_d['width'] > $law ) {
537
+ $law = $_d['width'];
538
+ $size = $_s;
539
+ }
 
 
540
  }
541
  }
542
 
543
+ return $size ? \wp_get_attachment_image_src( $id, $size ) : false;
 
 
 
544
  }
545
  }
inc/classes/generate-ldjson.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -104,7 +106,6 @@ class Generate_Ldjson extends Generate_Image {
104
  *
105
  * @param string $key The JSON data key.
106
  * @param array $entry The JSON data entry.
107
- * @return array The JSON data for $key.
108
  */
109
  protected function build_json_data_cache( $key, array $entry ) {
110
  $this->cache_json_data( false, $key, $entry );
@@ -281,7 +282,9 @@ class Generate_Ldjson extends Generate_Image {
281
 
282
  $sameurls = [];
283
  foreach ( $sameurls_options as $_o ) {
 
284
  $_ov = $this->get_option( $_o ) ?: '';
 
285
  if ( $_ov )
286
  $sameurls[] = \esc_url_raw( $_ov, [ 'https', 'http' ] );
287
  }
@@ -321,13 +324,42 @@ class Generate_Ldjson extends Generate_Image {
321
  'the_seo_framework_knowledge_logo',
322
  [
323
  ( $get_option ? $this->get_option( 'knowledge_logo_url' ) : false )
324
- ?: $this->get_site_icon()
325
  ?: '',
326
  $get_option,
327
  ]
328
  );
329
  }
330
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  /**
332
  * Generates LD+JSON Breadcrumbs script.
333
  *
@@ -342,10 +374,12 @@ class Generate_Ldjson extends Generate_Image {
342
 
343
  $output = '';
344
 
345
- if ( $this->is_single() || $this->is_wc_product() ) {
346
- $output = $this->get_ld_json_breadcrumbs_post();
347
- } elseif ( ! $this->is_real_front_page() && $this->is_page() ) {
348
- $output = $this->get_ld_json_breadcrumbs_page();
 
 
349
  }
350
 
351
  return $output;
@@ -356,12 +390,13 @@ class Generate_Ldjson extends Generate_Image {
356
  *
357
  * @since 2.9.3
358
  * @since 3.1.0 Now always generates something, regardless of parents.
 
359
  *
360
  * @return string LD+JSON breadcrumbs script for Pages.
361
  */
362
  public function get_ld_json_breadcrumbs_page() {
363
 
364
- $items = [];
365
  $parents = array_reverse( \get_post_ancestors( $this->get_the_real_ID() ) );
366
 
367
  $position = 1; // 0 is the homepage.
@@ -370,10 +405,10 @@ class Generate_Ldjson extends Generate_Image {
370
  ++$position;
371
 
372
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
373
- $parent_name = $this->get_raw_custom_field_title( [ 'id' => $parent_id ] )
374
- ?: ( $this->get_generated_single_post_title( $parent_id ) ?: $this->get_static_untitled_title() );
375
  } else {
376
- $parent_name = $this->get_generated_single_post_title( $parent_id ) ?: $this->get_static_untitled_title();
377
  }
378
 
379
  $crumb = [
@@ -389,10 +424,6 @@ class Generate_Ldjson extends Generate_Image {
389
  ],
390
  ];
391
 
392
- $image = $this->get_schema_image( $parent_id );
393
- if ( $image )
394
- $crumb['item']['image'] = $image;
395
-
396
  $items[] = $crumb;
397
  }
398
 
@@ -465,7 +496,7 @@ class Generate_Ldjson extends Generate_Image {
465
 
466
  $terms = \wp_list_pluck( $terms, 'parent', 'term_id' );
467
 
468
- $parents = [];
469
  $assigned_ids = [];
470
 
471
  //* Fetch cats children id's, if any.
@@ -502,14 +533,13 @@ class Generate_Ldjson extends Generate_Image {
502
  if ( ! $tree_ids )
503
  return '';
504
 
505
- $primary_term = $this->get_primary_term( $post_id, $taxonomy );
506
  $primary_term_id = $primary_term ? (int) $primary_term->term_id : 0;
507
 
508
  $filtered = false;
509
  /**
510
  * Only get one crumb.
511
  * If a category has multiple trees, it will filter until found.
512
- * @since 3.0.0
513
  */
514
  if ( $primary_term_id ) {
515
  $_trees = $this->filter_ld_json_breadcrumb_trees( $tree_ids, $primary_term_id );
@@ -532,12 +562,21 @@ class Generate_Ldjson extends Generate_Image {
532
  foreach ( $tree_ids as $pos => $child_id ) :
533
  $position = $pos + 2;
534
 
 
 
 
 
 
 
535
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
536
- $cat_name = $this->get_raw_custom_field_title( [ 'id' => $child_id, 'taxonomy' => $taxonomy ] )
537
- ?: ( $this->get_generated_single_term_title( \get_term( $child_id, $taxonomy ) ) ?: $this->get_static_untitled_title() );
 
538
  } else {
539
- $cat_name = $this->get_generated_single_term_title( \get_term( $child_id, $taxonomy ) ) ?: $this->get_static_untitled_title();
 
540
  }
 
541
 
542
  //* Store in cache.
543
  $items[] = [
@@ -547,13 +586,9 @@ class Generate_Ldjson extends Generate_Image {
547
  '@id' => $this->get_schema_url_id(
548
  'breadcrumb',
549
  'create',
550
- [
551
- 'id' => $child_id,
552
- 'taxonomy' => $taxonomy,
553
- ]
554
  ),
555
  'name' => $this->escape_title( $cat_name ),
556
- // 'image' => $this->get_schema_image( $child_id ),
557
  ],
558
  ];
559
  endforeach;
@@ -650,6 +685,7 @@ class Generate_Ldjson extends Generate_Image {
650
  * @since 3.2.2: 1. The title now works for the homepage as blog.
651
  * 2. The image has been disabled for the homepage as blog.
652
  * - I couldn't fix it without evading the API, which is bad.
 
653
  * @staticvar array $crumb
654
  *
655
  * @return array The HomePage crumb entry.
@@ -663,9 +699,9 @@ class Generate_Ldjson extends Generate_Image {
663
  $front_id = $this->get_the_front_page_ID();
664
 
665
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
666
- $title = $this->get_raw_custom_field_title( [ 'id' => $front_id ] ) ?: $this->get_blogname();
667
  } else {
668
- $title = $this->get_raw_generated_title( [ 'id' => $front_id ] ) ?: $this->get_blogname();
669
  }
670
 
671
  $crumb = [
@@ -677,9 +713,6 @@ class Generate_Ldjson extends Generate_Image {
677
  ],
678
  ];
679
 
680
- if ( $image = $this->get_schema_image( $front_id, true ) )
681
- $crumb['item']['image'] = $image;
682
-
683
  return $crumb;
684
  }
685
 
@@ -688,6 +721,7 @@ class Generate_Ldjson extends Generate_Image {
688
  *
689
  * @since 2.9.3
690
  * @since 3.0.0 Removed @id output to allow for more same-page schema items.
 
691
  * @staticvar array $crumb
692
  *
693
  * @param int $position The previous crumb position.
@@ -706,12 +740,16 @@ class Generate_Ldjson extends Generate_Image {
706
 
707
  $post_id = $this->get_the_real_ID();
708
 
 
709
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
710
- $name = $this->get_raw_custom_field_title( [ 'id' => $post_id ] )
711
- ?: ( $this->get_generated_single_post_title( $post_id ) ?: $this->get_static_untitled_title() );
 
712
  } else {
713
- $name = $this->get_generated_single_post_title( $post_id ) ?: $this->get_static_untitled_title();
 
714
  }
 
715
 
716
  $crumb = [
717
  '@type' => 'ListItem',
@@ -722,9 +760,6 @@ class Generate_Ldjson extends Generate_Image {
722
  ],
723
  ];
724
 
725
- if ( $image = $this->get_schema_image( $post_id, true ) )
726
- $crumb['item']['image'] = $image;
727
-
728
  return $crumb;
729
  }
730
 
@@ -800,8 +835,6 @@ class Generate_Ldjson extends Generate_Image {
800
  /**
801
  * Determines whether to use the SEO title or only the fallback page title.
802
  *
803
- * NOTE: Does not affect transient cache.
804
- *
805
  * @since 2.9.0
806
  * @staticvar bool $cache
807
  *
@@ -812,9 +845,9 @@ class Generate_Ldjson extends Generate_Image {
812
  static $cache = null;
813
 
814
  /**
815
- * Determines whether to use the SEO title or only the fallback page title in breadcrumbs.
816
  * @since 2.9.0
817
  * @param bool $use_seo_title Whether to use the SEO title.
 
818
  */
819
  return isset( $cache ) ? $cache : $cache = (bool) \apply_filters( 'the_seo_framework_use_breadcrumb_seo_title', true );
820
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Generate_Ldjson
4
+ * @subpackage The_SEO_Framework\Getters\Schema
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
106
  *
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 );
282
 
283
  $sameurls = [];
284
  foreach ( $sameurls_options as $_o ) {
285
+
286
  $_ov = $this->get_option( $_o ) ?: '';
287
+
288
  if ( $_ov )
289
  $sameurls[] = \esc_url_raw( $_ov, [ 'https', 'http' ] );
290
  }
324
  'the_seo_framework_knowledge_logo',
325
  [
326
  ( $get_option ? $this->get_option( 'knowledge_logo_url' ) : false )
327
+ ?: Builders\Images::get_site_icon_image_details()->current()['url']
328
  ?: '',
329
  $get_option,
330
  ]
331
  );
332
  }
333
 
334
+ /**
335
+ * Returns image URL suitable for Schema items.
336
+ *
337
+ * These are images that are strictly assigned to the Post or Page, fallbacks are omitted.
338
+ * Themes should compliment these. If not, then Open Graph should at least compliment these.
339
+ * If that's not even true, then I don't know what happens. But then you're in a grey area...
340
+ *
341
+ * @since 4.0.0
342
+ * @uses $this->get_image_details()
343
+ * @ignore Not used internally, only externally.
344
+ *
345
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
346
+ * Leave null to autodetermine query.
347
+ * @param bool $details Whether to return all details, or just a simple URL.
348
+ * @return string|array $url The Schema.org safe image.
349
+ */
350
+ public function get_safe_schema_image( $args = null, $details = false ) {
351
+
352
+ static $image_details = null;
353
+
354
+ if ( ! isset( $image_details ) )
355
+ $image_details = current( $this->get_image_details( $args, true, 'schema' ) );
356
+
357
+ if ( $details )
358
+ return $image_details;
359
+
360
+ return isset( $image_details['url'] ) ? $image_details['url'] : '';
361
+ }
362
+
363
  /**
364
  * Generates LD+JSON Breadcrumbs script.
365
  *
374
 
375
  $output = '';
376
 
377
+ if ( $this->is_singular() && ! $this->is_real_front_page() ) {
378
+ if ( $this->is_single() ) {
379
+ $output = $this->get_ld_json_breadcrumbs_post();
380
+ } else {
381
+ $output = $this->get_ld_json_breadcrumbs_page();
382
+ }
383
  }
384
 
385
  return $output;
390
  *
391
  * @since 2.9.3
392
  * @since 3.1.0 Now always generates something, regardless of parents.
393
+ * @since 4.0.0 Removed the image input requirement.
394
  *
395
  * @return string LD+JSON breadcrumbs script for Pages.
396
  */
397
  public function get_ld_json_breadcrumbs_page() {
398
 
399
+ $items = [];
400
  $parents = array_reverse( \get_post_ancestors( $this->get_the_real_ID() ) );
401
 
402
  $position = 1; // 0 is the homepage.
405
  ++$position;
406
 
407
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
408
+ $parent_name = $this->get_filtered_raw_custom_field_title( [ 'id' => $parent_id ] )
409
+ ?: $this->get_filtered_raw_generated_title( [ 'id' => $parent_id ] );
410
  } else {
411
+ $parent_name = $this->get_filtered_raw_generated_title( [ 'id' => $parent_id ] );
412
  }
413
 
414
  $crumb = [
424
  ],
425
  ];
426
 
 
 
 
 
427
  $items[] = $crumb;
428
  }
429
 
496
 
497
  $terms = \wp_list_pluck( $terms, 'parent', 'term_id' );
498
 
499
+ $parents = [];
500
  $assigned_ids = [];
501
 
502
  //* Fetch cats children id's, if any.
533
  if ( ! $tree_ids )
534
  return '';
535
 
536
+ $primary_term = $this->get_primary_term( $post_id, $taxonomy );
537
  $primary_term_id = $primary_term ? (int) $primary_term->term_id : 0;
538
 
539
  $filtered = false;
540
  /**
541
  * Only get one crumb.
542
  * If a category has multiple trees, it will filter until found.
 
543
  */
544
  if ( $primary_term_id ) {
545
  $_trees = $this->filter_ld_json_breadcrumb_trees( $tree_ids, $primary_term_id );
562
  foreach ( $tree_ids as $pos => $child_id ) :
563
  $position = $pos + 2;
564
 
565
+ $_generator_args = [
566
+ 'id' => $child_id,
567
+ 'taxonomy' => $taxonomy,
568
+ ];
569
+
570
+ // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
571
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
572
+ $cat_name = $this->get_filtered_raw_custom_field_title( $_generator_args )
573
+ ?: $this->get_generated_single_term_title( \get_term( $child_id, $taxonomy ) )
574
+ ?: $this->get_static_untitled_title();
575
  } else {
576
+ $cat_name = $this->get_generated_single_term_title( \get_term( $child_id, $taxonomy ) )
577
+ ?: $this->get_static_untitled_title();
578
  }
579
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
580
 
581
  //* Store in cache.
582
  $items[] = [
586
  '@id' => $this->get_schema_url_id(
587
  'breadcrumb',
588
  'create',
589
+ $_generator_args
 
 
 
590
  ),
591
  'name' => $this->escape_title( $cat_name ),
 
592
  ],
593
  ];
594
  endforeach;
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 couldn't fix it without evading the API, which is bad.
688
+ * @since 4.0.0 Removed the image input requirement.
689
  * @staticvar array $crumb
690
  *
691
  * @return array The HomePage crumb entry.
699
  $front_id = $this->get_the_front_page_ID();
700
 
701
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
702
+ $title = $this->get_filtered_raw_custom_field_title( [ 'id' => $front_id ] ) ?: $this->get_blogname();
703
  } else {
704
+ $title = $this->get_filtered_raw_generated_title( [ 'id' => $front_id ] ) ?: $this->get_blogname();
705
  }
706
 
707
  $crumb = [
713
  ],
714
  ];
715
 
 
 
 
716
  return $crumb;
717
  }
718
 
721
  *
722
  * @since 2.9.3
723
  * @since 3.0.0 Removed @id output to allow for more same-page schema items.
724
+ * @since 4.0.0 Removed the image input requirement.
725
  * @staticvar array $crumb
726
  *
727
  * @param int $position The previous crumb position.
740
 
741
  $post_id = $this->get_the_real_ID();
742
 
743
+ // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
744
  if ( $this->ld_json_breadcrumbs_use_seo_title() ) {
745
+ $name = $this->get_filtered_raw_custom_field_title( [ 'id' => $post_id ] )
746
+ ?: $this->get_generated_single_post_title( $post_id )
747
+ ?: $this->get_static_untitled_title();
748
  } else {
749
+ $name = $this->get_generated_single_post_title( $post_id )
750
+ ?: $this->get_static_untitled_title();
751
  }
752
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
753
 
754
  $crumb = [
755
  '@type' => 'ListItem',
760
  ],
761
  ];
762
 
 
 
 
763
  return $crumb;
764
  }
765
 
835
  /**
836
  * Determines whether to use the SEO title or only the fallback page title.
837
  *
 
 
838
  * @since 2.9.0
839
  * @staticvar bool $cache
840
  *
845
  static $cache = null;
846
 
847
  /**
 
848
  * @since 2.9.0
849
  * @param bool $use_seo_title Whether to use the SEO title.
850
+ * NOTE: Changing this does not affect the transient cache; wait for it to clear.
851
  */
852
  return isset( $cache ) ? $cache : $cache = (bool) \apply_filters( 'the_seo_framework_use_breadcrumb_seo_title', true );
853
  }
inc/classes/generate-title.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -50,8 +52,10 @@ class Generate_Title extends Generate_Description {
50
  */
51
  public function get_title( $args = null, $escape = true ) {
52
 
 
53
  $title = $this->get_custom_field_title( $args, false )
54
- ?: $this->get_generated_title( $args, false ); // precision alignment ok.
 
55
 
56
  return $escape ? $this->escape_title( $title ) : $title;
57
  }
@@ -60,6 +64,7 @@ class Generate_Title extends Generate_Description {
60
  * Returns the custom user-inputted title.
61
  *
62
  * @since 3.1.0
 
63
  *
64
  * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
65
  * Leave null to autodetermine query.
@@ -68,30 +73,17 @@ class Generate_Title extends Generate_Description {
68
  */
69
  public function get_custom_field_title( $args = null, $escape = true ) {
70
 
71
- /**
72
- * Filters the title from custom field, if any.
73
- *
74
- * @since 3.1.0
75
- *
76
- * @param string $title The title.
77
- * @param array $args The title arguments.
78
- */
79
- $title = (string) \apply_filters_ref_array( 'the_seo_framework_title_from_custom_field', [
80
- $this->get_raw_custom_field_title( $args ),
81
- $args,
82
- ] );
83
 
84
  if ( $title ) {
85
- //? Only add protection if the query is autodetermined, and on a real page.
86
- $this->merge_title_protection( $title, $args );
87
- if ( null === $args
88
- && ! ( $this->is_404() || $this->is_admin() ) ) {
89
  $this->merge_title_pagination( $title );
90
- }
91
 
92
- if ( $this->use_title_branding( $args ) ) {
93
  $this->merge_title_branding( $title, $args );
94
- }
95
  }
96
 
97
  return $escape ? $this->escape_title( $title ) : $title;
@@ -101,10 +93,11 @@ class Generate_Title extends Generate_Description {
101
  * Returns the autogenerated meta title.
102
  *
103
  * @since 3.1.0
104
- * @since 3.x.x 1. Added check for title protection.
105
  * 2. Moved check for title pagination.
 
106
  * @uses $this->s_title_raw() : This is the same method used to prepare custom title on save.
107
- * @uses $this->build_generated_title()
108
  *
109
  * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
110
  * Leave null to autodetermine query.
@@ -113,19 +106,7 @@ class Generate_Title extends Generate_Description {
113
  */
114
  public function get_generated_title( $args = null, $escape = true ) {
115
 
116
- /**
117
- * Filters the title from query.
118
- *
119
- * @NOTE: This filter doesn't consistently run on the SEO Settings page.
120
- * You might want to avoid this filter on the homepage.
121
- * @since 3.1.0
122
- * @param string $title The title.
123
- * @param array $args The title arguments.
124
- */
125
- $title = (string) \apply_filters_ref_array( 'the_seo_framework_title_from_generation', [
126
- $this->get_raw_generated_title( $args ),
127
- $args,
128
- ] );
129
 
130
  if ( $this->use_title_protection( $args ) )
131
  $this->merge_title_protection( $title, $args );
@@ -141,6 +122,63 @@ class Generate_Title extends Generate_Description {
141
  return $escape ? $this->escape_title( $title ) : $title;
142
  }
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  /**
145
  * Returns the Twitter meta title.
146
  * Falls back to Open Graph title.
@@ -156,8 +194,10 @@ class Generate_Title extends Generate_Description {
156
  */
157
  public function get_twitter_title( $args = null, $escape = true ) {
158
 
 
159
  $title = $this->get_twitter_title_from_custom_field( $args, false )
160
- ?: $this->get_generated_twitter_title( $args, false ); // precision alignment ok.
 
161
 
162
  return $escape ? $this->escape_title( $title ) : $title;
163
  }
@@ -191,6 +231,7 @@ class Generate_Title extends Generate_Description {
191
  *
192
  * @since 3.1.0
193
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
 
194
  * @see $this->get_twitter_title()
195
  * @see $this->get_twitter_title_from_custom_field()
196
  *
@@ -199,24 +240,29 @@ class Generate_Title extends Generate_Description {
199
  protected function get_custom_twitter_title_from_query() {
200
 
201
  $title = '';
202
-
203
  if ( $this->is_real_front_page() ) {
204
  if ( $this->is_static_frontpage() ) {
205
  $title = $this->get_option( 'homepage_twitter_title' )
206
- ?: $this->get_custom_field( '_twitter_title' )
207
  ?: $this->get_option( 'homepage_og_title' )
208
- ?: $this->get_custom_field( '_open_graph_title' )
209
- ?: ''; // precision alignment ok.
210
  } else {
211
  $title = $this->get_option( 'homepage_twitter_title' )
212
  ?: $this->get_option( 'homepage_og_title' )
213
- ?: ''; // precision alignment ok.
214
  }
215
  } elseif ( $this->is_singular() ) {
216
- $title = $this->get_custom_field( '_twitter_title' )
217
- ?: $this->get_custom_field( '_open_graph_title' )
218
- ?: ''; // precision alignment ok.
 
 
 
 
219
  }
 
220
 
221
  return $title;
222
  }
@@ -227,6 +273,7 @@ class Generate_Title extends Generate_Description {
227
  *
228
  * @since 3.1.0
229
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
 
230
  * @see $this->get_twitter_title()
231
  * @see $this->get_twitter_title_from_custom_field()
232
  *
@@ -235,27 +282,29 @@ class Generate_Title extends Generate_Description {
235
  */
236
  protected function get_custom_twitter_title_from_args( array $args ) {
237
 
238
- $title = '';
239
-
240
  if ( $args['taxonomy'] ) {
241
- $title = '';
 
 
242
  } else {
243
  if ( $this->is_static_frontpage( $args['id'] ) ) {
244
  $title = $this->get_option( 'homepage_twitter_title' )
245
- ?: $this->get_custom_field( '_twitter_title', $args['id'] )
246
  ?: $this->get_option( 'homepage_og_title' )
247
- ?: $this->get_custom_field( '_open_graph_title', $args['id'] )
248
- ?: ''; // precision alignment ok.
249
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
250
  $title = $this->get_option( 'homepage_twitter_title' )
251
  ?: $this->get_option( 'homepage_og_title' )
252
- ?: ''; // precision alignment ok.
253
  } else {
254
- $title = $this->get_custom_field( '_twitter_title', $args['id'] )
255
- ?: $this->get_custom_field( '_open_graph_title', $args['id'] )
256
- ?: ''; // precision alignment ok.
257
  }
258
  }
 
259
 
260
  return $title;
261
  }
@@ -293,8 +342,10 @@ class Generate_Title extends Generate_Description {
293
  */
294
  public function get_open_graph_title( $args = null, $escape = true ) {
295
 
 
296
  $title = $this->get_open_graph_title_from_custom_field( $args, false )
297
- ?: $this->get_generated_open_graph_title( $args, false ); // precision alignment ok.
 
298
 
299
  return $escape ? $this->escape_title( $title ) : $title;
300
  }
@@ -328,6 +379,7 @@ class Generate_Title extends Generate_Description {
328
  *
329
  * @since 3.1.0
330
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
 
331
  * @see $this->get_open_graph_title()
332
  * @see $this->get_open_graph_title_from_custom_field()
333
  *
@@ -336,18 +388,21 @@ class Generate_Title extends Generate_Description {
336
  protected function get_custom_open_graph_title_from_query() {
337
 
338
  $title = '';
339
-
340
  if ( $this->is_real_front_page() ) {
341
  if ( $this->is_static_frontpage() ) {
342
  $title = $this->get_option( 'homepage_og_title' )
343
- ?: $this->get_custom_field( '_open_graph_title' )
344
- ?: ''; // precision alignment ok.
345
  } else {
346
  $title = $this->get_option( 'homepage_og_title' ) ?: '';
347
  }
348
  } elseif ( $this->is_singular() ) {
349
- $title = $this->get_custom_field( '_open_graph_title' ) ?: '';
 
 
350
  }
 
351
 
352
  return $title;
353
  }
@@ -358,6 +413,7 @@ class Generate_Title extends Generate_Description {
358
  *
359
  * @since 3.1.0
360
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
 
361
  * @see $this->get_open_graph_title()
362
  * @see $this->get_open_graph_title_from_custom_field()
363
  *
@@ -367,20 +423,21 @@ class Generate_Title extends Generate_Description {
367
  protected function get_custom_open_graph_title_from_args( array $args ) {
368
 
369
  $title = '';
370
-
371
  if ( $args['taxonomy'] ) {
372
- $title = '';
373
  } else {
374
  if ( $this->is_static_frontpage( $args['id'] ) ) {
375
  $title = $this->get_option( 'homepage_og_title' )
376
- ?: $this->get_custom_field( '_open_graph_title', $args['id'] )
377
- ?: ''; // Precision alignment ok.
378
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
379
  $title = $this->get_option( 'homepage_og_title' ) ?: '';
380
  } else {
381
- $title = $this->get_custom_field( '_open_graph_title', $args['id'] ) ?: '';
382
  }
383
  }
 
384
 
385
  return $title;
386
  }
@@ -442,21 +499,21 @@ class Generate_Title extends Generate_Description {
442
  protected function get_custom_field_title_from_query() {
443
 
444
  $title = '';
445
-
446
  if ( $this->is_real_front_page() ) {
447
  if ( $this->is_static_frontpage() ) {
448
  $title = $this->get_option( 'homepage_title' )
449
- ?: $this->get_custom_field( '_genesis_title' )
450
- ?: ''; // precision alignment ok.
451
  } else {
452
  $title = $this->get_option( 'homepage_title' ) ?: '';
453
  }
454
  } elseif ( $this->is_singular() ) {
455
- $title = $this->get_custom_field( '_genesis_title' ) ?: '';
456
  } elseif ( $this->is_term_meta_capable() ) {
457
- $_data = $this->get_term_meta( $this->get_the_real_ID() );
458
- $title = ! empty( $_data['doctitle'] ) ? $_data['doctitle'] : '';
459
  }
 
460
 
461
  return $title;
462
  }
@@ -470,26 +527,27 @@ class Generate_Title extends Generate_Description {
470
  * @internal
471
  * @see $this->get_raw_custom_field_title()
472
  *
 
473
  * @return string The custom title.
474
  */
475
  protected function get_custom_field_title_from_args( array $args ) {
476
 
477
  $title = '';
478
-
479
  if ( $args['taxonomy'] ) {
480
- $_data = $this->get_term_meta( $args['id'] );
481
- $title = ! empty( $_data['doctitle'] ) ? $_data['doctitle'] : '';
482
  } else {
483
  if ( $this->is_static_frontpage( $args['id'] ) ) {
484
  $title = $this->get_option( 'homepage_title' )
485
- ?: $this->get_custom_field( '_genesis_title', $args['id'] )
486
- ?: ''; // precision alignment ok.
487
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
488
  $title = $this->get_option( 'homepage_title' ) ?: '';
489
  } else {
490
- $title = $this->get_custom_field( '_genesis_title', $args['id'] ) ?: '';
491
  }
492
  }
 
493
 
494
  return $title;
495
  }
@@ -562,6 +620,7 @@ class Generate_Title extends Generate_Description {
562
  foreach ( $functions as $function ) {
563
  $it = 10;
564
  $i = 0;
 
565
  while ( $priority = \has_filter( $filter, $function ) ) {
566
  $filtered[ $filter ][ $priority ][] = $function;
567
  \remove_filter( $filter, $function, $priority );
@@ -657,6 +716,7 @@ class Generate_Title extends Generate_Description {
657
  * @see WP Core get_the_archive_title()
658
  *
659
  * @since 3.1.0
 
660
  *
661
  * @param \WP_Term|\WP_Error|null $term The Term object or error. Leave null to autodetermine query.
662
  * @return string The generated archive title, not escaped.
@@ -694,14 +754,15 @@ class Generate_Title extends Generate_Description {
694
  $title = $this->get_generated_single_term_title( $term );
695
  /* translators: Category archive title. 1: Category name */
696
  $title = $use_prefix ? sprintf( \__( 'Category: %s', 'default' ), $title ) : $title;
697
- } elseif ( 'tag' === $_tax ) {
698
  $title = $this->get_generated_single_term_title( $term );
699
  /* translators: Tag archive title. 1: Tag name */
700
  $title = $use_prefix ? sprintf( \__( 'Tag: %s', 'default' ), $title ) : $title;
701
  } else {
702
- $title = $this->get_generated_single_term_title( $term );
 
703
 
704
- if ( $use_prefix && $_prefix = $this->get_tax_type_label( $_tax ) ) {
705
  /* translators: Taxonomy term archive title. 1: Taxonomy singular name, 2: Current taxonomy term */
706
  $title = sprintf( \__( '%1$s: %2$s', 'autodescription' ), $_prefix, $title );
707
  }
@@ -761,9 +822,10 @@ class Generate_Title extends Generate_Description {
761
  /* translators: Post type archive title. 1: Post type name */
762
  $title = $use_prefix ? sprintf( \__( 'Archives: %s', 'default' ), $title ) : $title;
763
  } elseif ( $this->is_tax() ) {
764
- $title = $this->get_generated_single_term_title( $term );
 
765
 
766
- if ( $use_prefix && $_prefix = $this->get_tax_type_label( $_tax ) ) {
767
  /* translators: Taxonomy term archive title. 1: Taxonomy singular name, 2: Current taxonomy term */
768
  $title = sprintf( \__( '%1$s: %2$s', 'autodescription' ), $_prefix, $title );
769
  }
@@ -777,8 +839,8 @@ class Generate_Title extends Generate_Description {
777
  *
778
  * @since 3.0.4
779
  *
780
- * @param string $title Archive title to be displayed.
781
- * @param \WP_Term $term The term object.
782
  */
783
  return \apply_filters( 'the_seo_framework_generated_archive_title', $title, $term );
784
  }
@@ -824,8 +886,10 @@ class Generate_Title extends Generate_Description {
824
  * @see WP Core single_term_title()
825
  *
826
  * @since 3.1.0
 
 
827
  *
828
- * @param null|\WP_Term $term The term name, required in the admin area.
829
  * @return string The generated single term title.
830
  */
831
  public function get_generated_single_term_title( $term = null ) {
@@ -836,7 +900,7 @@ class Generate_Title extends Generate_Description {
836
  $term_name = '';
837
 
838
  if ( isset( $term->name ) ) {
839
- if ( $this->is_category() || 'category' === $term->taxonomy ) {
840
  /**
841
  * Filter the category archive page title.
842
  *
@@ -845,7 +909,7 @@ class Generate_Title extends Generate_Description {
845
  * @param string $term_name Category name for archive being displayed.
846
  */
847
  $term_name = \apply_filters( 'single_cat_title', $term->name );
848
- } elseif ( $this->is_tag() || 'tag' === $term->taxonomy ) {
849
  /**
850
  * Filter the tag archive page title.
851
  *
@@ -854,7 +918,7 @@ class Generate_Title extends Generate_Description {
854
  * @param string $term_name Tag name for archive being displayed.
855
  */
856
  $term_name = \apply_filters( 'single_tag_title', $term->name );
857
- } elseif ( $this->is_tax() || $this->is_archive_admin() ) {
858
  /**
859
  * Filter the custom taxonomy archive page title.
860
  *
@@ -863,12 +927,6 @@ class Generate_Title extends Generate_Description {
863
  * @param string $term_name Term name for archive being displayed.
864
  */
865
  $term_name = \apply_filters( 'single_term_title', $term->name );
866
- } else {
867
- /**
868
- * Don't filter when query is unknown.
869
- * The filters don't pass the term; so, they imply the developer knows the term from query.
870
- */
871
- $term_name = $term->name;
872
  }
873
  }
874
 
@@ -998,8 +1056,7 @@ class Generate_Title extends Generate_Description {
998
 
999
  if ( $this->is_real_front_page() ) {
1000
  $addition = $this->get_home_page_tagline();
1001
- //? Brilliant. TODO FIXME: Do an "upgrade" of this option at a 3.1.2+ release, switching title with additions in the settings description.
1002
- $seplocation = 'left' === $this->get_home_title_seplocation() ? 'right' : 'left';
1003
  } else {
1004
  $addition = $this->get_blogname();
1005
  $seplocation = $this->get_title_seplocation();
@@ -1021,8 +1078,7 @@ class Generate_Title extends Generate_Description {
1021
 
1022
  if ( ! $args['taxonomy'] && $this->is_real_front_page_by_id( $args['id'] ) ) {
1023
  $addition = $this->get_home_page_tagline();
1024
- //? Brilliant. TODO FIXME: Do an "upgrade" of this option at a 3.1.2+ release, switching title with additions in the settings description.
1025
- $seplocation = 'left' === $this->get_home_title_seplocation() ? 'right' : 'left';
1026
  } else {
1027
  $addition = $this->get_blogname();
1028
  $seplocation = $this->get_title_seplocation();
@@ -1047,6 +1103,7 @@ class Generate_Title extends Generate_Description {
1047
  if ( $paged >= 2 || $page >= 2 ) {
1048
  $sep = $this->get_title_separator();
1049
 
 
1050
  $paging = sprintf( \__( 'Page %s', 'default' ), max( $paged, $page ) );
1051
 
1052
  if ( \is_rtl() ) {
@@ -1097,7 +1154,7 @@ class Generate_Title extends Generate_Description {
1097
  * Default 'Private: %s'.
1098
  * @param WP_Post $post Current post object.
1099
  */
1100
- // phpcs:ignore -- WordPress doesn't have a comment, either.
1101
  $protected_title_format = (string) \apply_filters( 'protected_title_format', \__( 'Protected: %s', 'default' ), $post );
1102
  $title = sprintf( $protected_title_format, $title );
1103
  } elseif ( isset( $post->post_status ) && 'private' === $post->post_status ) {
@@ -1112,7 +1169,7 @@ class Generate_Title extends Generate_Description {
1112
  * Default 'Private: %s'.
1113
  * @param WP_Post $post Current post object.
1114
  */
1115
- // phpcs:ignore -- WordPress doesn't have a comment, either.
1116
  $private_title_format = (string) \apply_filters( 'private_title_format', \__( 'Private: %s', 'default' ), $post );
1117
  $title = sprintf( $private_title_format, $title );
1118
  }
@@ -1145,6 +1202,7 @@ class Generate_Title extends Generate_Description {
1145
  * 2. The first parameter is now $home
1146
  * 3. Removed caching.
1147
  * 4. Removed filters.
 
1148
  *
1149
  * @param bool $home The home separator location.
1150
  * @return string The separator location.
@@ -1158,6 +1216,7 @@ class Generate_Title extends Generate_Description {
1158
  *
1159
  * @since 2.6.0
1160
  * @since 3.1.0 Removed first parameter.
 
1161
  *
1162
  * @return string The Seplocation for the homepage.
1163
  */
@@ -1170,7 +1229,7 @@ class Generate_Title extends Generate_Description {
1170
  *
1171
  * NOTE: This does not guarantee that protection is to be added.
1172
  *
1173
- * @since 3.x.x
1174
  * @see $this->merge_title_protection()
1175
  *
1176
  * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
@@ -1192,11 +1251,8 @@ class Generate_Title extends Generate_Description {
1192
  /**
1193
  * Determines whether to add or remove title pagination additions.
1194
  *
 
1195
  * NOTE: This does not guarantee that pagination is to be added.
1196
- * @see $this->paged()
1197
- * @see $this->page()
1198
- *
1199
- * @since 3.x.x
1200
  * @see $this->merge_title_pagination()
1201
  *
1202
  * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
@@ -1207,7 +1263,7 @@ class Generate_Title extends Generate_Description {
1207
 
1208
  //? Only add pagination if the query is autodetermined, and on a real page.
1209
  if ( null === $args ) {
1210
- if ( $this->is_404() || $this->is_admin() ) {
1211
  $use = false;
1212
  } else {
1213
  $use = true;
@@ -1245,8 +1301,9 @@ class Generate_Title extends Generate_Description {
1245
 
1246
  /**
1247
  * @since 3.1.2
1248
- * @param bool $use
1249
- * @param array|null $args
 
1250
  */
1251
  return \apply_filters_ref_array( 'the_seo_framework_use_title_branding', [ $use, $args ] );
1252
  }
@@ -1255,6 +1312,8 @@ class Generate_Title extends Generate_Description {
1255
  * Determines whether to add or remove title branding additions in the query.
1256
  *
1257
  * @since 3.2.2
 
 
1258
  * @see $this->use_title_branding()
1259
  *
1260
  * @return bool
@@ -1263,8 +1322,10 @@ class Generate_Title extends Generate_Description {
1263
 
1264
  if ( $this->is_real_front_page() ) {
1265
  $use = $this->use_home_page_title_tagline();
1266
- } elseif ( $this->is_singular() && ! $this->use_singular_title_branding( $this->get_the_real_ID() ) ) {
1267
- $use = false;
 
 
1268
  } else {
1269
  $use = ! $this->get_option( 'title_rem_additions' );
1270
  }
@@ -1276,6 +1337,7 @@ class Generate_Title extends Generate_Description {
1276
  * Determines whether to add or remove title branding additions from provided arguments.
1277
  *
1278
  * @since 3.2.2
 
1279
  * @see $this->use_title_branding()
1280
  *
1281
  * @param array $args The query arguments. Accepts 'id' and 'taxonomy'.
@@ -1284,14 +1346,12 @@ class Generate_Title extends Generate_Description {
1284
  protected function use_title_branding_from_args( array $args ) {
1285
 
1286
  if ( $args['taxonomy'] ) {
1287
- $use = ! $this->get_option( 'title_rem_additions' );
1288
  } else {
1289
  if ( $this->is_real_front_page_by_id( $args['id'] ) ) {
1290
  $use = $this->use_home_page_title_tagline();
1291
- } elseif ( ! $this->use_singular_title_branding( $args['id'] ) ) {
1292
- $use = false;
1293
  } else {
1294
- $use = ! $this->get_option( 'title_rem_additions' );
1295
  }
1296
  }
1297
 
@@ -1313,7 +1373,7 @@ class Generate_Title extends Generate_Description {
1313
  * Determines whether to add homepage tagline.
1314
  *
1315
  * @since 2.6.0
1316
- * @since 3.0.4 Now checks for custom tagline or blogname existence.
1317
  *
1318
  * @return bool
1319
  */
@@ -1322,26 +1382,45 @@ class Generate_Title extends Generate_Description {
1322
  }
1323
 
1324
  /**
1325
- * Determines whether to add the tagline.
1326
  *
1327
  * @since 3.1.0
1328
  *
1329
  * @param int $id The post ID. Optional.
1330
  * @return bool
1331
  */
1332
- public function use_singular_title_branding( $id = null ) {
1333
- return ! $this->get_custom_field( '_tsf_title_no_blogname', $id ) && ! $this->get_option( 'title_rem_additions' );
 
 
 
 
 
 
 
 
 
 
 
 
1334
  }
1335
 
1336
  /**
1337
  * Returns the homepage tagline from option or bloginfo, when set.
1338
  *
1339
  * @since 3.0.4
 
 
1340
  * @uses $this->get_blogdescription(), this method already trims.
1341
  *
1342
  * @return string The trimmed tagline.
1343
  */
1344
  public function get_home_page_tagline() {
1345
- return $this->s_title_raw( trim( $this->get_option( 'homepage_title_tagline' ) ) ?: $this->get_blogdescription() ?: '' );
 
 
 
 
 
1346
  }
1347
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Generate_Title
4
+ * @subpackage The_SEO_Framework\Getters\Title
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
52
  */
53
  public function get_title( $args = null, $escape = true ) {
54
 
55
+ // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
56
  $title = $this->get_custom_field_title( $args, false )
57
+ ?: $this->get_generated_title( $args, false );
58
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
59
 
60
  return $escape ? $this->escape_title( $title ) : $title;
61
  }
64
  * Returns the custom user-inputted title.
65
  *
66
  * @since 3.1.0
67
+ * @since 4.0.0 Moved the filter to a separated method.
68
  *
69
  * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
70
  * Leave null to autodetermine query.
73
  */
74
  public function get_custom_field_title( $args = null, $escape = true ) {
75
 
76
+ $title = $this->get_filtered_raw_custom_field_title( $args );
 
 
 
 
 
 
 
 
 
 
 
77
 
78
  if ( $title ) {
79
+ if ( $this->use_title_protection( $args ) )
80
+ $this->merge_title_protection( $title, $args );
81
+
82
+ if ( $this->use_title_pagination( $args ) )
83
  $this->merge_title_pagination( $title );
 
84
 
85
+ if ( $this->use_title_branding( $args ) )
86
  $this->merge_title_branding( $title, $args );
 
87
  }
88
 
89
  return $escape ? $this->escape_title( $title ) : $title;
93
  * Returns the autogenerated meta title.
94
  *
95
  * @since 3.1.0
96
+ * @since 3.2.4 1. Added check for title protection.
97
  * 2. Moved check for title pagination.
98
+ * @since 4.0.0 Moved the filter to a separated method.
99
  * @uses $this->s_title_raw() : This is the same method used to prepare custom title on save.
100
+ * @uses $this->get_filtered_raw_generated_title()
101
  *
102
  * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
103
  * Leave null to autodetermine query.
106
  */
107
  public function get_generated_title( $args = null, $escape = true ) {
108
 
109
+ $title = $this->get_filtered_raw_generated_title( $args );
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
  if ( $this->use_title_protection( $args ) )
112
  $this->merge_title_protection( $title, $args );
122
  return $escape ? $this->escape_title( $title ) : $title;
123
  }
124
 
125
+ /**
126
+ * Returns the raw filtered custom field meta title.
127
+ *
128
+ * @since 4.0.0
129
+ *
130
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
131
+ * Leave null to autodetermine query.
132
+ * @return string The raw generated title output.
133
+ */
134
+ public function get_filtered_raw_custom_field_title( $args ) {
135
+ /**
136
+ * Filters the title from custom field, if any.
137
+ *
138
+ * @since 3.1.0
139
+ *
140
+ * @param string $title The title.
141
+ * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
142
+ * Is null when query is autodetermined.
143
+ */
144
+ return (string) \apply_filters_ref_array(
145
+ 'the_seo_framework_title_from_custom_field',
146
+ [
147
+ $this->get_raw_custom_field_title( $args ),
148
+ $args,
149
+ ]
150
+ );
151
+ }
152
+
153
+ /**
154
+ * Returns the raw filtered autogenerated meta title.
155
+ *
156
+ * @since 4.0.0
157
+ *
158
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
159
+ * Leave null to autodetermine query.
160
+ * @return string The raw generated title output.
161
+ */
162
+ public function get_filtered_raw_generated_title( $args ) {
163
+ /**
164
+ * Filters the title from query.
165
+ *
166
+ * @NOTE: This filter doesn't consistently run on the SEO Settings page.
167
+ * You may want to avoid this filter for the homepage, by returning the default value.
168
+ * @since 3.1.0
169
+ * @param string $title The title.
170
+ * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
171
+ * Is null when query is autodetermined.
172
+ */
173
+ return (string) \apply_filters_ref_array(
174
+ 'the_seo_framework_title_from_generation',
175
+ [
176
+ $this->get_raw_generated_title( $args ),
177
+ $args,
178
+ ]
179
+ );
180
+ }
181
+
182
  /**
183
  * Returns the Twitter meta title.
184
  * Falls back to Open Graph title.
194
  */
195
  public function get_twitter_title( $args = null, $escape = true ) {
196
 
197
+ // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
198
  $title = $this->get_twitter_title_from_custom_field( $args, false )
199
+ ?: $this->get_generated_twitter_title( $args, false );
200
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
201
 
202
  return $escape ? $this->escape_title( $title ) : $title;
203
  }
231
  *
232
  * @since 3.1.0
233
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
234
+ * @since 4.0.0 Added term meta item checks.
235
  * @see $this->get_twitter_title()
236
  * @see $this->get_twitter_title_from_custom_field()
237
  *
240
  protected function get_custom_twitter_title_from_query() {
241
 
242
  $title = '';
243
+ // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
244
  if ( $this->is_real_front_page() ) {
245
  if ( $this->is_static_frontpage() ) {
246
  $title = $this->get_option( 'homepage_twitter_title' )
247
+ ?: $this->get_post_meta_item( '_twitter_title' )
248
  ?: $this->get_option( 'homepage_og_title' )
249
+ ?: $this->get_post_meta_item( '_open_graph_title' )
250
+ ?: '';
251
  } else {
252
  $title = $this->get_option( 'homepage_twitter_title' )
253
  ?: $this->get_option( 'homepage_og_title' )
254
+ ?: '';
255
  }
256
  } elseif ( $this->is_singular() ) {
257
+ $title = $this->get_post_meta_item( '_twitter_title' )
258
+ ?: $this->get_post_meta_item( '_open_graph_title' )
259
+ ?: '';
260
+ } elseif ( $this->is_term_meta_capable() ) {
261
+ $title = $this->get_term_meta_item( 'tw_title' )
262
+ ?: $this->get_term_meta_item( 'og_title' )
263
+ ?: '';
264
  }
265
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
266
 
267
  return $title;
268
  }
273
  *
274
  * @since 3.1.0
275
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
276
+ * @since 4.0.0 Added term meta item checks.
277
  * @see $this->get_twitter_title()
278
  * @see $this->get_twitter_title_from_custom_field()
279
  *
282
  */
283
  protected function get_custom_twitter_title_from_args( array $args ) {
284
 
285
+ // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
 
286
  if ( $args['taxonomy'] ) {
287
+ $title = $this->get_term_meta_item( 'tw_title', $args['id'] )
288
+ ?: $this->get_term_meta_item( 'og_title', $args['id'] )
289
+ ?: '';
290
  } else {
291
  if ( $this->is_static_frontpage( $args['id'] ) ) {
292
  $title = $this->get_option( 'homepage_twitter_title' )
293
+ ?: $this->get_post_meta_item( '_twitter_title', $args['id'] )
294
  ?: $this->get_option( 'homepage_og_title' )
295
+ ?: $this->get_post_meta_item( '_open_graph_title', $args['id'] )
296
+ ?: '';
297
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
298
  $title = $this->get_option( 'homepage_twitter_title' )
299
  ?: $this->get_option( 'homepage_og_title' )
300
+ ?: '';
301
  } else {
302
+ $title = $this->get_post_meta_item( '_twitter_title', $args['id'] )
303
+ ?: $this->get_post_meta_item( '_open_graph_title', $args['id'] )
304
+ ?: '';
305
  }
306
  }
307
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
308
 
309
  return $title;
310
  }
342
  */
343
  public function get_open_graph_title( $args = null, $escape = true ) {
344
 
345
+ // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
346
  $title = $this->get_open_graph_title_from_custom_field( $args, false )
347
+ ?: $this->get_generated_open_graph_title( $args, false );
348
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
349
 
350
  return $escape ? $this->escape_title( $title ) : $title;
351
  }
379
  *
380
  * @since 3.1.0
381
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
382
+ * @since 4.0.0 Added term meta item checks.
383
  * @see $this->get_open_graph_title()
384
  * @see $this->get_open_graph_title_from_custom_field()
385
  *
388
  protected function get_custom_open_graph_title_from_query() {
389
 
390
  $title = '';
391
+ // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
392
  if ( $this->is_real_front_page() ) {
393
  if ( $this->is_static_frontpage() ) {
394
  $title = $this->get_option( 'homepage_og_title' )
395
+ ?: $this->get_post_meta_item( '_open_graph_title' )
396
+ ?: '';
397
  } else {
398
  $title = $this->get_option( 'homepage_og_title' ) ?: '';
399
  }
400
  } elseif ( $this->is_singular() ) {
401
+ $title = $this->get_post_meta_item( '_open_graph_title' ) ?: '';
402
+ } elseif ( $this->is_term_meta_capable() ) {
403
+ $title = $this->get_term_meta_item( 'og_title' ) ?: '';
404
  }
405
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
406
 
407
  return $title;
408
  }
413
  *
414
  * @since 3.1.0
415
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
416
+ * @since 4.0.0 Added term meta item checks.
417
  * @see $this->get_open_graph_title()
418
  * @see $this->get_open_graph_title_from_custom_field()
419
  *
423
  protected function get_custom_open_graph_title_from_args( array $args ) {
424
 
425
  $title = '';
426
+ // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
427
  if ( $args['taxonomy'] ) {
428
+ $title = $this->get_term_meta_item( 'og_title', $args['id'] ) ?: '';
429
  } else {
430
  if ( $this->is_static_frontpage( $args['id'] ) ) {
431
  $title = $this->get_option( 'homepage_og_title' )
432
+ ?: $this->get_post_meta_item( '_open_graph_title', $args['id'] )
433
+ ?: '';
434
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
435
  $title = $this->get_option( 'homepage_og_title' ) ?: '';
436
  } else {
437
+ $title = $this->get_post_meta_item( '_open_graph_title', $args['id'] ) ?: '';
438
  }
439
  }
440
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
441
 
442
  return $title;
443
  }
499
  protected function get_custom_field_title_from_query() {
500
 
501
  $title = '';
502
+ // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
503
  if ( $this->is_real_front_page() ) {
504
  if ( $this->is_static_frontpage() ) {
505
  $title = $this->get_option( 'homepage_title' )
506
+ ?: $this->get_post_meta_item( '_genesis_title' )
507
+ ?: '';
508
  } else {
509
  $title = $this->get_option( 'homepage_title' ) ?: '';
510
  }
511
  } elseif ( $this->is_singular() ) {
512
+ $title = $this->get_post_meta_item( '_genesis_title' ) ?: '';
513
  } elseif ( $this->is_term_meta_capable() ) {
514
+ $title = $this->get_term_meta_item( 'doctitle' ) ?: '';
 
515
  }
516
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
517
 
518
  return $title;
519
  }
527
  * @internal
528
  * @see $this->get_raw_custom_field_title()
529
  *
530
+ * @param array $args The query arguments. Accepts 'id' and 'taxonomy'.
531
  * @return string The custom title.
532
  */
533
  protected function get_custom_field_title_from_args( array $args ) {
534
 
535
  $title = '';
536
+ // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
537
  if ( $args['taxonomy'] ) {
538
+ $title = $this->get_term_meta_item( 'doctitle', $args['id'] ) ?: '';
 
539
  } else {
540
  if ( $this->is_static_frontpage( $args['id'] ) ) {
541
  $title = $this->get_option( 'homepage_title' )
542
+ ?: $this->get_post_meta_item( '_genesis_title', $args['id'] )
543
+ ?: '';
544
  } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
545
  $title = $this->get_option( 'homepage_title' ) ?: '';
546
  } else {
547
+ $title = $this->get_post_meta_item( '_genesis_title', $args['id'] ) ?: '';
548
  }
549
  }
550
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
551
 
552
  return $title;
553
  }
620
  foreach ( $functions as $function ) {
621
  $it = 10;
622
  $i = 0;
623
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
624
  while ( $priority = \has_filter( $filter, $function ) ) {
625
  $filtered[ $filter ][ $priority ][] = $function;
626
  \remove_filter( $filter, $function, $priority );
716
  * @see WP Core get_the_archive_title()
717
  *
718
  * @since 3.1.0
719
+ * @since 4.0.2 Now asserts the correct tag taxonomy condition.
720
  *
721
  * @param \WP_Term|\WP_Error|null $term The Term object or error. Leave null to autodetermine query.
722
  * @return string The generated archive title, not escaped.
754
  $title = $this->get_generated_single_term_title( $term );
755
  /* translators: Category archive title. 1: Category name */
756
  $title = $use_prefix ? sprintf( \__( 'Category: %s', 'default' ), $title ) : $title;
757
+ } elseif ( 'post_tag' === $_tax ) {
758
  $title = $this->get_generated_single_term_title( $term );
759
  /* translators: Tag archive title. 1: Tag name */
760
  $title = $use_prefix ? sprintf( \__( 'Tag: %s', 'default' ), $title ) : $title;
761
  } else {
762
+ $title = $this->get_generated_single_term_title( $term );
763
+ $_prefix = $use_prefix ? $this->get_tax_type_label( $_tax ) : '';
764
 
765
+ if ( $_prefix ) {
766
  /* translators: Taxonomy term archive title. 1: Taxonomy singular name, 2: Current taxonomy term */
767
  $title = sprintf( \__( '%1$s: %2$s', 'autodescription' ), $_prefix, $title );
768
  }
822
  /* translators: Post type archive title. 1: Post type name */
823
  $title = $use_prefix ? sprintf( \__( 'Archives: %s', 'default' ), $title ) : $title;
824
  } elseif ( $this->is_tax() ) {
825
+ $title = $this->get_generated_single_term_title( $term );
826
+ $_prefix = $use_prefix ? $this->get_tax_type_label( $_tax ) : '';
827
 
828
+ if ( $_prefix ) {
829
  /* translators: Taxonomy term archive title. 1: Taxonomy singular name, 2: Current taxonomy term */
830
  $title = sprintf( \__( '%1$s: %2$s', 'autodescription' ), $_prefix, $title );
831
  }
839
  *
840
  * @since 3.0.4
841
  *
842
+ * @param string $title Archive title to be displayed.
843
+ * @param \WP_Term $term The term object.
844
  */
845
  return \apply_filters( 'the_seo_framework_generated_archive_title', $title, $term );
846
  }
886
  * @see WP Core single_term_title()
887
  *
888
  * @since 3.1.0
889
+ * @since 4.0.0 No longer redundantly tests the query, now only uses the term input or queried object.
890
+ * @since 4.0.2 Now asserts the correct tag taxonomy condition.
891
  *
892
+ * @param null|\WP_Term $term The term name, required in the admin area.
893
  * @return string The generated single term title.
894
  */
895
  public function get_generated_single_term_title( $term = null ) {
900
  $term_name = '';
901
 
902
  if ( isset( $term->name ) ) {
903
+ if ( 'category' === $term->taxonomy ) {
904
  /**
905
  * Filter the category archive page title.
906
  *
909
  * @param string $term_name Category name for archive being displayed.
910
  */
911
  $term_name = \apply_filters( 'single_cat_title', $term->name );
912
+ } elseif ( 'post_tag' === $term->taxonomy ) {
913
  /**
914
  * Filter the tag archive page title.
915
  *
918
  * @param string $term_name Tag name for archive being displayed.
919
  */
920
  $term_name = \apply_filters( 'single_tag_title', $term->name );
921
+ } else {
922
  /**
923
  * Filter the custom taxonomy archive page title.
924
  *
927
  * @param string $term_name Term name for archive being displayed.
928
  */
929
  $term_name = \apply_filters( 'single_term_title', $term->name );
 
 
 
 
 
 
930
  }
931
  }
932
 
1056
 
1057
  if ( $this->is_real_front_page() ) {
1058
  $addition = $this->get_home_page_tagline();
1059
+ $seplocation = $this->get_home_title_seplocation();
 
1060
  } else {
1061
  $addition = $this->get_blogname();
1062
  $seplocation = $this->get_title_seplocation();
1078
 
1079
  if ( ! $args['taxonomy'] && $this->is_real_front_page_by_id( $args['id'] ) ) {
1080
  $addition = $this->get_home_page_tagline();
1081
+ $seplocation = $this->get_home_title_seplocation();
 
1082
  } else {
1083
  $addition = $this->get_blogname();
1084
  $seplocation = $this->get_title_seplocation();
1103
  if ( $paged >= 2 || $page >= 2 ) {
1104
  $sep = $this->get_title_separator();
1105
 
1106
+ // phpcs:ignore, WordPress.WP.I18n -- WP didn't add translator code either.
1107
  $paging = sprintf( \__( 'Page %s', 'default' ), max( $paged, $page ) );
1108
 
1109
  if ( \is_rtl() ) {
1154
  * Default 'Private: %s'.
1155
  * @param WP_Post $post Current post object.
1156
  */
1157
+ // phpcs:ignore, WordPress.WP.I18n -- WordPress doesn't have a comment, either.
1158
  $protected_title_format = (string) \apply_filters( 'protected_title_format', \__( 'Protected: %s', 'default' ), $post );
1159
  $title = sprintf( $protected_title_format, $title );
1160
  } elseif ( isset( $post->post_status ) && 'private' === $post->post_status ) {
1169
  * Default 'Private: %s'.
1170
  * @param WP_Post $post Current post object.
1171
  */
1172
+ // phpcs:ignore, WordPress.WP.I18n -- WordPress doesn't have a comment, either.
1173
  $private_title_format = (string) \apply_filters( 'private_title_format', \__( 'Private: %s', 'default' ), $post );
1174
  $title = sprintf( $private_title_format, $title );
1175
  }
1202
  * 2. The first parameter is now $home
1203
  * 3. Removed caching.
1204
  * 4. Removed filters.
1205
+ * @since 4.0.0 The homepage option's return value is now reversed from expected.
1206
  *
1207
  * @param bool $home The home separator location.
1208
  * @return string The separator location.
1216
  *
1217
  * @since 2.6.0
1218
  * @since 3.1.0 Removed first parameter.
1219
+ * @since 4.0.0 Left is now right, and right is now left.
1220
  *
1221
  * @return string The Seplocation for the homepage.
1222
  */
1229
  *
1230
  * NOTE: This does not guarantee that protection is to be added.
1231
  *
1232
+ * @since 3.2.4
1233
  * @see $this->merge_title_protection()
1234
  *
1235
  * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
1251
  /**
1252
  * Determines whether to add or remove title pagination additions.
1253
  *
1254
+ * @since 3.2.4
1255
  * NOTE: This does not guarantee that pagination is to be added.
 
 
 
 
1256
  * @see $this->merge_title_pagination()
1257
  *
1258
  * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
1263
 
1264
  //? Only add pagination if the query is autodetermined, and on a real page.
1265
  if ( null === $args ) {
1266
+ if ( $this->is_404() || \is_admin() ) {
1267
  $use = false;
1268
  } else {
1269
  $use = true;
1301
 
1302
  /**
1303
  * @since 3.1.2
1304
+ * @param string $use Whether to use branding.
1305
+ * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
1306
+ * Is null when query is autodetermined.
1307
  */
1308
  return \apply_filters_ref_array( 'the_seo_framework_use_title_branding', [ $use, $args ] );
1309
  }
1312
  * Determines whether to add or remove title branding additions in the query.
1313
  *
1314
  * @since 3.2.2
1315
+ * @since 4.0.0 Added use_taxonomical_title_branding() check.
1316
+ * @since 4.0.2 Removed contemned \is_post_type_archive() check for taxonomical branding.
1317
  * @see $this->use_title_branding()
1318
  *
1319
  * @return bool
1322
 
1323
  if ( $this->is_real_front_page() ) {
1324
  $use = $this->use_home_page_title_tagline();
1325
+ } elseif ( $this->is_singular() ) {
1326
+ $use = $this->use_singular_title_branding();
1327
+ } elseif ( $this->is_term_meta_capable() ) {
1328
+ $use = $this->use_taxonomical_title_branding();
1329
  } else {
1330
  $use = ! $this->get_option( 'title_rem_additions' );
1331
  }
1337
  * Determines whether to add or remove title branding additions from provided arguments.
1338
  *
1339
  * @since 3.2.2
1340
+ * @since 4.0.0 Added use_taxonomical_title_branding() check.
1341
  * @see $this->use_title_branding()
1342
  *
1343
  * @param array $args The query arguments. Accepts 'id' and 'taxonomy'.
1346
  protected function use_title_branding_from_args( array $args ) {
1347
 
1348
  if ( $args['taxonomy'] ) {
1349
+ $use = $this->use_taxonomical_title_branding( $args['id'] );
1350
  } else {
1351
  if ( $this->is_real_front_page_by_id( $args['id'] ) ) {
1352
  $use = $this->use_home_page_title_tagline();
 
 
1353
  } else {
1354
+ $use = $this->use_singular_title_branding( $args['id'] );
1355
  }
1356
  }
1357
 
1373
  * Determines whether to add homepage tagline.
1374
  *
1375
  * @since 2.6.0
1376
+ * @since 3.0.4 Now checks for custom tagline or blog name existence.
1377
  *
1378
  * @return bool
1379
  */
1382
  }
1383
 
1384
  /**
1385
+ * Determines whether to add the title tagline for the post.
1386
  *
1387
  * @since 3.1.0
1388
  *
1389
  * @param int $id The post ID. Optional.
1390
  * @return bool
1391
  */
1392
+ public function use_singular_title_branding( $id = 0 ) {
1393
+ return ! $this->get_post_meta_item( '_tsf_title_no_blogname', $id ) && ! $this->get_option( 'title_rem_additions' );
1394
+ }
1395
+
1396
+ /**
1397
+ * Determines whether to add the title tagline for the term.
1398
+ *
1399
+ * @since 4.0.0
1400
+ *
1401
+ * @param int $id The term ID. Optional.
1402
+ * @return bool
1403
+ */
1404
+ public function use_taxonomical_title_branding( $id = 0 ) {
1405
+ return ! $this->get_term_meta_item( 'title_no_blog_name', $id ) && ! $this->get_option( 'title_rem_additions' );
1406
  }
1407
 
1408
  /**
1409
  * Returns the homepage tagline from option or bloginfo, when set.
1410
  *
1411
  * @since 3.0.4
1412
+ * @since 4.0.0 Added caching.
1413
+ * @staticvar string $cache
1414
  * @uses $this->get_blogdescription(), this method already trims.
1415
  *
1416
  * @return string The trimmed tagline.
1417
  */
1418
  public function get_home_page_tagline() {
1419
+ static $cache;
1420
+ return $cache ?: $cache = $this->s_title_raw(
1421
+ trim( $this->get_option( 'homepage_title_tagline' ) )
1422
+ ?: $this->get_blogdescription()
1423
+ ?: ''
1424
+ );
1425
  }
1426
  }
inc/classes/generate-url.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -116,6 +118,7 @@ class Generate_Url extends Generate_Title {
116
  * The URL will never be paginated.
117
  *
118
  * @since 3.0.0
 
119
  * @uses $this->get_canonical_url()
120
  *
121
  * @param array $args The canonical URL arguments : {
@@ -127,14 +130,16 @@ class Generate_Url extends Generate_Title {
127
  */
128
  public function create_canonical_url( $args = [] ) {
129
 
 
 
 
130
  $defaults = [
131
  'id' => 0,
132
  'taxonomy' => '',
133
  'get_custom_field' => false,
134
  ];
135
- $args = array_merge( $defaults, $args );
136
 
137
- return $this->get_canonical_url( $args );
138
  }
139
 
140
  /**
@@ -150,7 +155,7 @@ class Generate_Url extends Generate_Title {
150
  public function get_canonical_url( $args = null ) {
151
 
152
  if ( $args ) {
153
- //= See $this->create_canonical_url().
154
  $canonical_url = $this->build_canonical_url( $args );
155
  $query = false;
156
  } else {
@@ -167,9 +172,8 @@ class Generate_Url extends Generate_Title {
167
  if ( $this->matches_this_domain( $canonical_url ) ) {
168
  $canonical_url = $this->set_preferred_url_scheme( $canonical_url );
169
  }
170
- $canonical_url = $this->clean_canonical_url( $canonical_url );
171
 
172
- return $canonical_url;
173
  }
174
 
175
  /**
@@ -177,43 +181,45 @@ class Generate_Url extends Generate_Title {
177
  *
178
  * @since 3.0.0
179
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
 
180
  * @see $this->create_canonical_url()
181
  *
182
- * @param array $args. Use $this->create_canonical_url().
183
  * @return string The canonical URL.
184
  */
185
  protected function build_canonical_url( array $args ) {
186
 
187
- //? extract(). See $this->create_canonical_url()
188
- foreach ( $args as $k => $v ) $$k = $v;
189
-
190
- $canonical_url = '';
191
 
192
- if ( $taxonomy ) {
193
- $canonical_url = $this->get_taxonomial_canonical_url( $id, $taxonomy );
 
 
 
194
  } else {
195
- if ( $this->is_static_frontpage( $id ) ) {
196
- if ( $get_custom_field ) {
197
- $canonical_url = $this->get_singular_custom_canonical_url( $id );
198
  }
199
- $canonical_url = $canonical_url ?: $this->get_home_canonical_url();
200
- } elseif ( $this->is_real_front_page_by_id( $id ) ) {
201
- $canonical_url = $this->get_home_canonical_url();
202
- } elseif ( $id ) {
203
- if ( $get_custom_field ) {
204
- $canonical_url = $this->get_singular_custom_canonical_url( $id );
205
  }
206
- $canonical_url = $canonical_url ?: $this->get_singular_canonical_url( $id );
207
  }
208
  }
209
 
210
- return $canonical_url;
211
  }
212
 
213
  /**
214
  * Generates canonical URL from current query.
215
  *
216
  * @since 3.0.0
 
217
  * @see $this->get_canonical_url()
218
  *
219
  * @return string The canonical URL.
@@ -224,19 +230,21 @@ class Generate_Url extends Generate_Title {
224
  $url = '';
225
 
226
  if ( $this->is_real_front_page() ) {
227
- if ( $this->has_page_on_front() )
228
- $url = $this->get_singular_custom_canonical_url( $id );
229
- if ( ! $url )
 
230
  $url = $this->get_home_canonical_url();
 
231
  } elseif ( $this->is_singular() ) {
232
- $url = $this->get_singular_custom_canonical_url( $id );
233
- if ( ! $url )
234
- $url = $this->get_singular_canonical_url( $id );
235
  } elseif ( $this->is_archive() ) {
236
- if ( $this->is_category() || $this->is_tag() || $this->is_tax() ) {
237
- $url = $this->get_taxonomial_canonical_url( $id, $this->get_current_taxonomy() );
 
238
  } elseif ( \is_post_type_archive() ) {
239
- $url = $this->get_post_type_archive_canonical_url( $id );
240
  } elseif ( $this->is_author() ) {
241
  $url = $this->get_author_canonical_url( $id );
242
  } elseif ( $this->is_date() ) {
@@ -267,10 +275,10 @@ class Generate_Url extends Generate_Title {
267
  public function clean_canonical_url( $url ) {
268
 
269
  if ( $this->pretty_permalinks ) {
270
- $url = \esc_url( $url, [ 'http', 'https' ] );
271
  } else {
272
  //= Keep the &'s more readable.
273
- $url = \esc_url_raw( $url, [ 'http', 'https' ] );
274
  }
275
 
276
  return $url;
@@ -328,7 +336,7 @@ class Generate_Url extends Generate_Title {
328
  * @return string The custom canonical URL, if any.
329
  */
330
  public function get_singular_custom_canonical_url( $id ) {
331
- return $this->get_custom_field( '_genesis_canonical_uri', $id ) ?: '';
332
  }
333
 
334
  /**
@@ -359,19 +367,34 @@ class Generate_Url extends Generate_Title {
359
  return $url;
360
  }
361
 
 
 
 
 
 
 
 
 
 
 
 
 
362
  /**
363
  * Returns taxonomical canonical URL.
364
  * Automatically adds pagination if the ID matches the query.
365
  *
366
  * @since 3.0.0
 
 
367
  *
368
  * @param int $term_id The term ID.
369
  * @param string $taxonomy The taxonomy.
370
  * @return string The taxonomical canonical URL, if any.
371
  */
372
- public function get_taxonomial_canonical_url( $term_id, $taxonomy ) {
373
 
374
- $link = \get_term_link( $term_id, $taxonomy );
 
375
 
376
  if ( \is_wp_error( $link ) )
377
  return '';
@@ -388,31 +411,35 @@ class Generate_Url extends Generate_Title {
388
  * Returns post type archive canonical URL.
389
  *
390
  * @since 3.0.0
 
 
391
  *
392
- * @param int|string $post_type The post type archive ID or post type.
 
393
  * @return string The post type archive canonical URL, if any.
394
  */
395
- public function get_post_type_archive_canonical_url( $post_type ) {
396
 
397
  if ( is_int( $post_type ) ) {
398
- $term_id = (int) $post_type;
399
- $term = $this->fetch_the_term( $term_id );
 
400
 
401
- if ( $term instanceof \WP_Post_Type ) {
402
- $link = \get_post_type_archive_link( $term->name );
403
 
404
- if ( $term_id === $this->get_the_real_ID() ) {
405
- //= Adds pagination if ID matches query.
406
- $link = $this->add_url_pagination( $link, $this->paged(), true );
407
- }
408
- } else {
409
- $link = '';
410
- }
411
- } else {
412
- $link = \get_post_type_archive_link( $post_type );
413
  }
414
 
415
- return $link ?: '';
 
 
 
 
 
416
  }
417
 
418
  /**
@@ -469,17 +496,17 @@ class Generate_Url extends Generate_Title {
469
  switch ( $_get ) {
470
  case 'day':
471
  $_day = \get_query_var( 'day' );
472
- $_paginate = $_paginate && $_day == $day; // loose comparison OK.
473
  // No break. Get month too.
474
 
475
  case 'month':
476
  $_month = \get_query_var( 'monthnum' );
477
- $_paginate = $_paginate && $_month == $month; // loose comparison OK.
478
  // No break. Get year too.
479
 
480
  case 'year':
481
  $_year = \get_query_var( 'year' );
482
- $_paginate = $_paginate && $_year == $year; // loose comparison OK.
483
  break;
484
  }
485
 
@@ -528,6 +555,7 @@ class Generate_Url extends Generate_Title {
528
  * Can automatically be detected.
529
  *
530
  * @since 3.0.0
 
531
  * @staticvar string $scheme
532
  *
533
  * @return string The preferred URl scheme.
@@ -550,7 +578,7 @@ class Generate_Url extends Generate_Title {
550
 
551
  default:
552
  case 'automatic':
553
- $scheme = $this->is_ssl() ? 'https' : 'http';
554
  break;
555
  endswitch;
556
 
@@ -563,6 +591,22 @@ class Generate_Url extends Generate_Title {
563
  return $scheme;
564
  }
565
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566
  /**
567
  * Sets URL to preferred URL scheme.
568
  * Does not sanitize output.
@@ -583,16 +627,13 @@ class Generate_Url extends Generate_Title {
583
  * @since 2.4.2
584
  * @since 3.0.0 $use_filter now defaults to false.
585
  * @since 3.1.0 The third parameter ($use_filter) is now $deprecated.
 
586
  *
587
- * @param string $url Absolute url that includes a scheme.
588
- * @param string $scheme optional. Scheme to give $url. Currently 'http', 'https', 'login', 'login_post', 'admin', or 'relative'.
589
- * @param null|bool $deprecated Deprecated
590
  * @return string url with chosen scheme.
591
  */
592
- public function set_url_scheme( $url, $scheme = null, $deprecated = null ) {
593
-
594
- if ( null !== $deprecated )
595
- $this->_doing_it_wrong( __METHOD__, 'Third parameter is deprecated.', '3.1.0' );
596
 
597
  if ( empty( $scheme ) ) {
598
  $scheme = $this->is_ssl() ? 'https' : 'http';
@@ -605,7 +646,7 @@ class Generate_Url extends Generate_Title {
605
  $url = $this->make_fully_qualified_url( $url );
606
 
607
  if ( 'relative' === $scheme ) {
608
- $url = ltrim( preg_replace( '#^\w+://[^/]*#', '', $url ) );
609
 
610
  if ( '' !== $url && '/' === $url[0] )
611
  $url = '/' . ltrim( $url, "/ \t\n\r\0\x0B" );
@@ -740,9 +781,9 @@ class Generate_Url extends Generate_Title {
740
  * @since 3.0.0
741
  * @access private
742
  *
743
- * @param \WP_Term $term The category to use in the permalink.
744
- * @param array $terms Array of all categories (WP_Term objects) associated with the post.
745
- * @param \WP_Post $post The post in question.
746
  * @return \WP_Term The primary term.
747
  */
748
  public function _adjust_post_link_category( $term, $terms = null, $post = null ) {
@@ -784,6 +825,7 @@ class Generate_Url extends Generate_Title {
784
  'm' => isset( $_query['monthnum'] ) ? $_query['monthnum'] : '',
785
  'd' => isset( $_query['day'] ) ? $_query['day'] : '',
786
  ];
 
787
  $url = \add_query_arg( [ 'm' => implode( '', $_date ) ], $home );
788
  } elseif ( $this->is_author() ) {
789
  $url = \add_query_arg( [ 'author' => $id ], $home );
@@ -791,13 +833,11 @@ class Generate_Url extends Generate_Title {
791
  //* Generate shortlink for object type and slug.
792
  $object = \get_queried_object();
793
 
794
- $t = isset( $object->taxonomy ) ? urlencode( $object->taxonomy ) : '';
 
795
 
796
- if ( $t ) {
797
- $slug = isset( $object->slug ) ? urlencode( $object->slug ) : '';
798
-
799
- if ( $slug )
800
- $url = \add_query_arg( [ $t => $slug ], $home );
801
  }
802
  }
803
  } elseif ( $this->is_search() ) {
@@ -822,7 +862,7 @@ class Generate_Url extends Generate_Title {
822
  );
823
  }
824
 
825
- return \esc_url_raw( $url, [ 'http', 'https' ] );
826
  }
827
 
828
  /**
@@ -835,7 +875,7 @@ class Generate_Url extends Generate_Title {
835
  * 4. Removed WordPress Core `get_pagenum_link` filter.
836
  * @uses $this->get_paged_urls();
837
  *
838
- * @param string $prev_next Whether to get the previous or next page link.
839
  * Accepts 'prev' and 'next'.
840
  * @return string Escaped site Pagination URL
841
  */
@@ -866,10 +906,11 @@ class Generate_Url extends Generate_Title {
866
 
867
  if ( $this->has_custom_canonical_url() ) goto end;
868
 
 
869
  if ( $this->is_singular() && ! $this->is_singular_archive() && $this->is_multipage() ) {
870
  $_run = $this->is_real_front_page()
871
  ? $this->get_option( 'prev_next_frontpage' )
872
- : $this->get_option( 'prev_next_posts' ); // precision alignment ok.
873
 
874
  if ( ! $_run ) goto end;
875
 
@@ -878,7 +919,7 @@ class Generate_Url extends Generate_Title {
878
  } elseif ( $this->is_real_front_page() || $this->is_archive() || $this->is_singular_archive() || $this->is_search() ) {
879
  $_run = $this->is_real_front_page()
880
  ? $this->get_option( 'prev_next_frontpage' )
881
- : $this->get_option( 'prev_next_archives' ); // precision alignment ok.
882
 
883
  if ( ! $_run ) goto end;
884
 
@@ -887,6 +928,7 @@ class Generate_Url extends Generate_Title {
887
  } else {
888
  goto end;
889
  }
 
890
 
891
  $canonical = $this->remove_pagination_from_url( $this->get_current_canonical_url() );
892
 
@@ -922,7 +964,7 @@ class Generate_Url extends Generate_Title {
922
  if ( isset( $cache ) )
923
  return $cache;
924
 
925
- $parsed_url = \wp_parse_url( \get_home_url() );
926
 
927
  $host = isset( $parsed_url['host'] ) ? $parsed_url['host'] : '';
928
 
@@ -950,7 +992,7 @@ class Generate_Url extends Generate_Title {
950
  *
951
  * @since 2.6.0
952
  *
953
- * @param int $paged The current page number.
954
  * @param bool $singular Whether to allow plural and singular.
955
  * @param bool $plural Whether to allow plural regardless.
956
  *
@@ -970,10 +1012,16 @@ class Generate_Url extends Generate_Title {
970
  }
971
 
972
  /**
973
- * Makes a fully qualified URL from input. Always uses http to fix.
974
- * @see $this->set_url_scheme()
 
 
 
 
975
  *
976
  * @since 2.6.5
 
 
977
  *
978
  * @param string $url Required the current maybe not fully qualified URL.
979
  * @return string $url
@@ -989,6 +1037,23 @@ class Generate_Url extends Generate_Title {
989
  return $url;
990
  }
991
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
992
  /**
993
  * Appends given query to given URL.
994
  *
@@ -1019,4 +1084,37 @@ class Generate_Url extends Generate_Title {
1019
 
1020
  return $url;
1021
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1022
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Generate_Url
4
+ * @subpackage The_SEO_Framework\Getters\URL
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
118
  * The URL will never be paginated.
119
  *
120
  * @since 3.0.0
121
+ * @since 4.0.0 Now preemptively fixes the generation arguments, for easier implementation.
122
  * @uses $this->get_canonical_url()
123
  *
124
  * @param array $args The canonical URL arguments : {
130
  */
131
  public function create_canonical_url( $args = [] ) {
132
 
133
+ $this->fix_generation_args( $args );
134
+ $args = $args ?: [];
135
+
136
  $defaults = [
137
  'id' => 0,
138
  'taxonomy' => '',
139
  'get_custom_field' => false,
140
  ];
 
141
 
142
+ return $this->get_canonical_url( array_merge( $defaults, $args ) );
143
  }
144
 
145
  /**
155
  public function get_canonical_url( $args = null ) {
156
 
157
  if ( $args ) {
158
+ // See and use `$this->create_canonical_url()` instead.
159
  $canonical_url = $this->build_canonical_url( $args );
160
  $query = false;
161
  } else {
172
  if ( $this->matches_this_domain( $canonical_url ) ) {
173
  $canonical_url = $this->set_preferred_url_scheme( $canonical_url );
174
  }
 
175
 
176
+ return $this->clean_canonical_url( $canonical_url );
177
  }
178
 
179
  /**
181
  *
182
  * @since 3.0.0
183
  * @since 3.2.2 Now tests for the homepage as page prior getting custom field data.
184
+ * @since 4.0.0 Can now fetch custom canonical URL for terms.
185
  * @see $this->create_canonical_url()
186
  *
187
+ * @param array $args Use $this->create_canonical_url().
188
  * @return string The canonical URL.
189
  */
190
  protected function build_canonical_url( array $args ) {
191
 
192
+ $url = '';
 
 
 
193
 
194
+ if ( $args['taxonomy'] ) {
195
+ if ( $args['get_custom_field'] ) {
196
+ $url = $this->get_taxonomical_custom_canonical_url( $args['id'] );
197
+ }
198
+ $url = $url ?: $this->get_taxonomical_canonical_url( $args['id'], $args['taxonomy'] );
199
  } else {
200
+ if ( $this->is_static_frontpage( $args['id'] ) ) {
201
+ if ( $args['get_custom_field'] ) {
202
+ $url = $this->get_singular_custom_canonical_url( $args['id'] );
203
  }
204
+ $url = $url ?: $this->get_home_canonical_url();
205
+ } elseif ( $this->is_real_front_page_by_id( $args['id'] ) ) {
206
+ $url = $this->get_home_canonical_url();
207
+ } elseif ( $args['id'] ) {
208
+ if ( $args['get_custom_field'] ) {
209
+ $url = $this->get_singular_custom_canonical_url( $args['id'] );
210
  }
211
+ $url = $url ?: $this->get_singular_canonical_url( $args['id'] );
212
  }
213
  }
214
 
215
+ return $url;
216
  }
217
 
218
  /**
219
  * Generates canonical URL from current query.
220
  *
221
  * @since 3.0.0
222
+ * @since 4.0.0 Can now fetch custom canonical URL for terms.
223
  * @see $this->get_canonical_url()
224
  *
225
  * @return string The canonical URL.
230
  $url = '';
231
 
232
  if ( $this->is_real_front_page() ) {
233
+ if ( $this->has_page_on_front() ) {
234
+ $url = $this->get_singular_custom_canonical_url( $id )
235
+ ?: $this->get_home_canonical_url();
236
+ } else {
237
  $url = $this->get_home_canonical_url();
238
+ }
239
  } elseif ( $this->is_singular() ) {
240
+ $url = $this->get_singular_custom_canonical_url( $id )
241
+ ?: $this->get_singular_canonical_url( $id );
 
242
  } elseif ( $this->is_archive() ) {
243
+ if ( $this->is_term_meta_capable() ) {
244
+ $url = $this->get_taxonomical_custom_canonical_url( $id )
245
+ ?: $this->get_taxonomical_canonical_url( $id, $this->get_current_taxonomy() );
246
  } elseif ( \is_post_type_archive() ) {
247
+ $url = $this->get_post_type_archive_canonical_url();
248
  } elseif ( $this->is_author() ) {
249
  $url = $this->get_author_canonical_url( $id );
250
  } elseif ( $this->is_date() ) {
275
  public function clean_canonical_url( $url ) {
276
 
277
  if ( $this->pretty_permalinks ) {
278
+ $url = \esc_url( $url, [ 'https', 'http' ] );
279
  } else {
280
  //= Keep the &'s more readable.
281
+ $url = \esc_url_raw( $url, [ 'https', 'http' ] );
282
  }
283
 
284
  return $url;
336
  * @return string The custom canonical URL, if any.
337
  */
338
  public function get_singular_custom_canonical_url( $id ) {
339
+ return $this->get_post_meta_item( '_genesis_canonical_uri', $id ) ?: '';
340
  }
341
 
342
  /**
367
  return $url;
368
  }
369
 
370
+ /**
371
+ * Returns taxonomical custom field's canonical URL.
372
+ *
373
+ * @since 4.0.0
374
+ *
375
+ * @param int $term_id The term ID.
376
+ * @return string The custom canonical URL, if any.
377
+ */
378
+ public function get_taxonomical_custom_canonical_url( $term_id ) {
379
+ return $this->get_term_meta_item( 'canonical', $term_id ) ?: '';
380
+ }
381
+
382
  /**
383
  * Returns taxonomical canonical URL.
384
  * Automatically adds pagination if the ID matches the query.
385
  *
386
  * @since 3.0.0
387
+ * @since 4.0.0 1. Renamed from "get_taxonomical_canonical_url" (note the typo)
388
+ * 2. Now works on the admin-screens.
389
  *
390
  * @param int $term_id The term ID.
391
  * @param string $taxonomy The taxonomy.
392
  * @return string The taxonomical canonical URL, if any.
393
  */
394
+ public function get_taxonomical_canonical_url( $term_id, $taxonomy ) {
395
 
396
+ $term = \get_term( $term_id, $taxonomy );
397
+ $link = \get_term_link( $term, $taxonomy );
398
 
399
  if ( \is_wp_error( $link ) )
400
  return '';
411
  * Returns post type archive canonical URL.
412
  *
413
  * @since 3.0.0
414
+ * @since 4.0.0 : 1. Deprecated first parameter as integer. Use strings or null.
415
+ * 2. Now forwards post type object calling to WordPress' function.
416
  *
417
+ * @param null|string $post_type The post type archive's post type.
418
+ * Leave null to use query, and allow pagination.
419
  * @return string The post type archive canonical URL, if any.
420
  */
421
+ public function get_post_type_archive_canonical_url( $post_type = null ) {
422
 
423
  if ( is_int( $post_type ) ) {
424
+ $this->_doing_it_wrong( __METHOD__, 'Only send strings or null in the first parameter.', '4.0.0' );
425
+ $post_type = '';
426
+ }
427
 
428
+ $query = true;
 
429
 
430
+ if ( null === $post_type ) {
431
+ $post_type = \get_query_var( 'post_type' );
432
+ $post_type = is_array( $post_type ) ? reset( $post_type ) : $post_type;
433
+
434
+ $query = false;
 
 
 
 
435
  }
436
 
437
+ $link = \get_post_type_archive_link( $post_type ) ?: '';
438
+
439
+ if ( $query && $link )
440
+ $link = $this->add_url_pagination( $link, $this->paged(), true );
441
+
442
+ return $link;
443
  }
444
 
445
  /**
496
  switch ( $_get ) {
497
  case 'day':
498
  $_day = \get_query_var( 'day' );
499
+ $_paginate = $_paginate && $_day == $day; // phpcs:ignore, WordPress.PHP.StrictComparisons.LooseComparison
500
  // No break. Get month too.
501
 
502
  case 'month':
503
  $_month = \get_query_var( 'monthnum' );
504
+ $_paginate = $_paginate && $_month == $month; // phpcs:ignore, WordPress.PHP.StrictComparisons.LooseComparison
505
  // No break. Get year too.
506
 
507
  case 'year':
508
  $_year = \get_query_var( 'year' );
509
+ $_paginate = $_paginate && $_year == $year; // phpcs:ignore, WordPress.PHP.StrictComparisons.LooseComparison
510
  break;
511
  }
512
 
555
  * Can automatically be detected.
556
  *
557
  * @since 3.0.0
558
+ * @since 4.0.0 Now gets the "automatic" scheme from the WordPress home URL.
559
  * @staticvar string $scheme
560
  *
561
  * @return string The preferred URl scheme.
578
 
579
  default:
580
  case 'automatic':
581
+ $scheme = $this->detect_site_url_scheme();
582
  break;
583
  endswitch;
584
 
591
  return $scheme;
592
  }
593
 
594
+ /**
595
+ * Detects site's URL scheme from site options.
596
+ * Falls back to is_ssl() when the hom misconfigured via wp-config.php
597
+ *
598
+ * NOTE: Some (insecure, e.g. SP) implementations for the `WP_HOME` constant, where
599
+ * the scheme is interpreted from the request, may cause this to be unreliable.
600
+ * We're going to ignore those edge-cases; they're doing it wrong.
601
+ *
602
+ * @since 4.0.0
603
+ *
604
+ * @return string The detected URl scheme, lowercase.
605
+ */
606
+ public function detect_site_url_scheme() {
607
+ return strtolower( parse_url( \get_home_url(), PHP_URL_SCHEME ) ) ?: ( $this->is_ssl() ? 'https' : 'http' );
608
+ }
609
+
610
  /**
611
  * Sets URL to preferred URL scheme.
612
  * Does not sanitize output.
627
  * @since 2.4.2
628
  * @since 3.0.0 $use_filter now defaults to false.
629
  * @since 3.1.0 The third parameter ($use_filter) is now $deprecated.
630
+ * @since 4.0.0 Removed the deprecated parameter.
631
  *
632
+ * @param string $url Absolute url that includes a scheme.
633
+ * @param string $scheme Optional. Scheme to give $url. Currently 'http', 'https', 'login', 'login_post', 'admin', or 'relative'.
 
634
  * @return string url with chosen scheme.
635
  */
636
+ public function set_url_scheme( $url, $scheme = null ) {
 
 
 
637
 
638
  if ( empty( $scheme ) ) {
639
  $scheme = $this->is_ssl() ? 'https' : 'http';
646
  $url = $this->make_fully_qualified_url( $url );
647
 
648
  if ( 'relative' === $scheme ) {
649
+ $url = ltrim( preg_replace( '/^\w+:\/\/[^\/]*/', '', $url ) );
650
 
651
  if ( '' !== $url && '/' === $url[0] )
652
  $url = '/' . ltrim( $url, "/ \t\n\r\0\x0B" );
781
  * @since 3.0.0
782
  * @access private
783
  *
784
+ * @param \WP_Term $term The category to use in the permalink.
785
+ * @param array $terms Array of all categories (WP_Term objects) associated with the post.
786
+ * @param \WP_Post $post The post in question.
787
  * @return \WP_Term The primary term.
788
  */
789
  public function _adjust_post_link_category( $term, $terms = null, $post = null ) {
825
  'm' => isset( $_query['monthnum'] ) ? $_query['monthnum'] : '',
826
  'd' => isset( $_query['day'] ) ? $_query['day'] : '',
827
  ];
828
+
829
  $url = \add_query_arg( [ 'm' => implode( '', $_date ) ], $home );
830
  } elseif ( $this->is_author() ) {
831
  $url = \add_query_arg( [ 'author' => $id ], $home );
833
  //* Generate shortlink for object type and slug.
834
  $object = \get_queried_object();
835
 
836
+ $tax = isset( $object->taxonomy ) ? $object->taxonomy : '';
837
+ $slug = isset( $object->slug ) ? $object->slug : '';
838
 
839
+ if ( $tax && $slug ) {
840
+ $url = \add_query_arg( [ $tax => $slug ], $home );
 
 
 
841
  }
842
  }
843
  } elseif ( $this->is_search() ) {
862
  );
863
  }
864
 
865
+ return \esc_url_raw( $url, [ 'https', 'http' ] );
866
  }
867
 
868
  /**
875
  * 4. Removed WordPress Core `get_pagenum_link` filter.
876
  * @uses $this->get_paged_urls();
877
  *
878
+ * @param string $next_prev Whether to get the previous or next page link.
879
  * Accepts 'prev' and 'next'.
880
  * @return string Escaped site Pagination URL
881
  */
906
 
907
  if ( $this->has_custom_canonical_url() ) goto end;
908
 
909
+ // phpcs:disable, WordPress.WhiteSpace.PrecisionAlignment
910
  if ( $this->is_singular() && ! $this->is_singular_archive() && $this->is_multipage() ) {
911
  $_run = $this->is_real_front_page()
912
  ? $this->get_option( 'prev_next_frontpage' )
913
+ : $this->get_option( 'prev_next_posts' );
914
 
915
  if ( ! $_run ) goto end;
916
 
919
  } elseif ( $this->is_real_front_page() || $this->is_archive() || $this->is_singular_archive() || $this->is_search() ) {
920
  $_run = $this->is_real_front_page()
921
  ? $this->get_option( 'prev_next_frontpage' )
922
+ : $this->get_option( 'prev_next_archives' );
923
 
924
  if ( ! $_run ) goto end;
925
 
928
  } else {
929
  goto end;
930
  }
931
+ // phpcs:enable, WordPress.WhiteSpace.PrecisionAlignment
932
 
933
  $canonical = $this->remove_pagination_from_url( $this->get_current_canonical_url() );
934
 
964
  if ( isset( $cache ) )
965
  return $cache;
966
 
967
+ $parsed_url = parse_url( \get_home_url() );
968
 
969
  $host = isset( $parsed_url['host'] ) ? $parsed_url['host'] : '';
970
 
992
  *
993
  * @since 2.6.0
994
  *
995
+ * @param int $paged The current page number.
996
  * @param bool $singular Whether to allow plural and singular.
997
  * @param bool $plural Whether to allow plural regardless.
998
  *
1012
  }
1013
 
1014
  /**
1015
+ * Makes a fully qualified URL by adding the scheme prefix.
1016
+ * Always adds http prefix, not https.
1017
+ *
1018
+ * NOTE: Expects the URL to have either a scheme, or a relative scheme set.
1019
+ * Domain-relative URLs aren't parsed correctly.
1020
+ * '/path/to/folder/` will become `http:///path/to/folder/`
1021
  *
1022
  * @since 2.6.5
1023
+ * @see `$this->set_url_scheme()` to set the correct scheme.
1024
+ * @see `$this->convert_to_url_if_path()` to create URLs from paths.
1025
  *
1026
  * @param string $url Required the current maybe not fully qualified URL.
1027
  * @return string $url
1037
  return $url;
1038
  }
1039
 
1040
+ /**
1041
+ * Makes a fully qualified URL from any input.
1042
+ *
1043
+ * @since 4.0.0
1044
+ * @see `$this->s_relative_url()` to make URLs relative.
1045
+ *
1046
+ * @param string $path Either the URL or path. Will always be transformed to the current domain.
1047
+ * @param string $url The URL to add the path to. Defaults to the current home URL.
1048
+ * @return string $url
1049
+ */
1050
+ public function convert_to_url_if_path( $path, $url = '' ) {
1051
+ return \WP_Http::make_absolute_url(
1052
+ $path,
1053
+ \trailingslashit( $url ?: $this->set_preferred_url_scheme( $this->get_home_host() ) )
1054
+ );
1055
+ }
1056
+
1057
  /**
1058
  * Appends given query to given URL.
1059
  *
1084
 
1085
  return $url;
1086
  }
1087
+
1088
+ /**
1089
+ * Tests if input URL matches current domain.
1090
+ *
1091
+ * @since 2.9.4
1092
+ * @since 4.0.0 Improved performance.
1093
+ *
1094
+ * @param string $url The URL to test. Required.
1095
+ * @return bool true on match, false otherwise.
1096
+ */
1097
+ public function matches_this_domain( $url ) {
1098
+
1099
+ if ( ! $url )
1100
+ return false;
1101
+
1102
+ static $home_domain;
1103
+
1104
+ if ( ! $home_domain ) {
1105
+ $home_domain = \esc_url_raw( \get_home_url(), [ 'https', 'http' ] );
1106
+ //= Simply convert to HTTPS/HTTP based on is_ssl()
1107
+ $home_domain = $this->set_url_scheme( $home_domain );
1108
+ }
1109
+
1110
+ $url = \esc_url_raw( $url, [ 'https', 'http' ] );
1111
+ //= Simply convert to HTTPS/HTTP based on is_ssl()
1112
+ $url = $this->set_url_scheme( $url );
1113
+
1114
+ //= If they start with the same, we can assume it's the same domain.
1115
+ if ( 0 === stripos( $url, $home_domain ) )
1116
+ return true;
1117
+
1118
+ return false;
1119
+ }
1120
  }
inc/classes/generate.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -42,10 +44,13 @@ class Generate extends User_Data {
42
  */
43
  protected function fix_generation_args( &$args ) {
44
  if ( is_array( $args ) ) {
45
- $args = array_merge( [
46
- 'id' => 0,
47
- 'taxonomy' => '',
48
- ], $args );
 
 
 
49
  } elseif ( is_numeric( $args ) ) {
50
  $args = [
51
  'id' => (int) $args,
@@ -68,110 +73,443 @@ class Generate extends User_Data {
68
  * @since 3.1.0 1. Simplified statements, often (not always) speeding things up.
69
  * 2. Now checks for wc_shop and blog types for pagination.
70
  * 3. Removed noydir.
 
 
 
 
 
71
  * @global \WP_Query $wp_query
72
  *
73
- * @return array|null robots
 
 
 
 
 
 
 
 
 
74
  */
75
- public function robots_meta() {
 
 
 
 
 
 
 
76
 
77
- //* Defaults
78
  $meta = [
79
- 'noindex' => $this->get_option( 'site_noindex' ) ? 'noindex' : '',
80
- 'nofollow' => $this->get_option( 'site_nofollow' ) ? 'nofollow' : '',
81
- 'noarchive' => $this->get_option( 'site_noarchive' ) ? 'noarchive' : '',
 
 
 
82
  ];
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  //* Check homepage SEO settings, set noindex, nofollow and noarchive
85
  if ( $this->is_real_front_page() ) {
86
- $meta['noindex'] = $this->get_option( 'homepage_noindex' ) ? 'noindex' : $meta['noindex'];
87
- $meta['nofollow'] = $this->get_option( 'homepage_nofollow' ) ? 'nofollow' : $meta['nofollow'];
88
- $meta['noarchive'] = $this->get_option( 'homepage_noarchive' ) ? 'noarchive' : $meta['noarchive'];
89
-
90
- if ( $this->get_option( 'home_paged_noindex' ) && ( $this->page() > 1 || $this->paged() > 1 ) ) {
91
- $meta['noindex'] = 'noindex';
92
- }
 
93
  } else {
94
  global $wp_query;
95
 
96
  /**
97
  * Check for 404, or if archive is empty: set noindex for those.
98
  * Don't check this on the homepage. The homepage is sacred in this regard,
99
- * because page builders and templates likely take over.
100
- * @since 2.2.8
101
  *
102
- * @todo maybe create option
103
- * @priority so low... 3.0.0+
104
  */
105
- if ( isset( $wp_query->post_count ) && 0 === $wp_query->post_count )
106
- $meta['noindex'] = 'noindex';
107
-
108
- $is_archive = $this->is_archive();
109
-
110
- if ( $this->get_option( 'paged_noindex' ) && $this->paged() > 1 ) {
111
- if ( $is_archive || $this->is_singular_archive() )
112
- $meta['noindex'] = $this->get_option( 'paged_noindex' ) ? 'noindex' : $meta['noindex'];
 
 
113
  }
114
 
115
- if ( $is_archive ) {
116
- $term_data = $this->get_current_term_meta();
117
-
118
- if ( $term_data ) {
119
- $meta['noindex'] = ! empty( $term_data['noindex'] ) ? 'noindex' : $meta['noindex'];
120
- $meta['nofollow'] = ! empty( $term_data['nofollow'] ) ? 'nofollow' : $meta['nofollow'];
121
- $meta['noarchive'] = ! empty( $term_data['noarchive'] ) ? 'noarchive' : $meta['noarchive'];
122
- }
123
-
124
  //* If on custom Taxonomy page, but not a category or tag, then should've received specific term SEO settings.
125
  if ( $this->is_category() ) {
126
- $meta['noindex'] = $this->get_option( 'category_noindex' ) ? 'noindex' : $meta['noindex'];
127
- $meta['nofollow'] = $this->get_option( 'category_nofollow' ) ? 'nofollow' : $meta['nofollow'];
128
- $meta['noarchive'] = $this->get_option( 'category_noindex' ) ? 'noarchive' : $meta['noarchive'];
129
  } elseif ( $this->is_tag() ) {
130
- $meta['noindex'] = $this->get_option( 'tag_noindex' ) ? 'noindex' : $meta['noindex'];
131
- $meta['nofollow'] = $this->get_option( 'tag_nofollow' ) ? 'nofollow' : $meta['nofollow'];
132
- $meta['noarchive'] = $this->get_option( 'tag_noindex' ) ? 'noarchive' : $meta['noarchive'];
133
  } elseif ( $this->is_author() ) {
134
- $meta['noindex'] = $this->get_option( 'author_noindex' ) ? 'noindex' : $meta['noindex'];
135
- $meta['nofollow'] = $this->get_option( 'author_nofollow' ) ? 'nofollow' : $meta['nofollow'];
136
- $meta['noarchive'] = $this->get_option( 'author_noarchive' ) ? 'noarchive' : $meta['noarchive'];
137
  } elseif ( $this->is_date() ) {
138
- $meta['noindex'] = $this->get_option( 'date_noindex' ) ? 'noindex' : $meta['noindex'];
139
- $meta['nofollow'] = $this->get_option( 'date_nofollow' ) ? 'nofollow' : $meta['nofollow'];
140
- $meta['noarchive'] = $this->get_option( 'date_noarchive' ) ? 'noarchive' : $meta['noarchive'];
141
  }
142
  } elseif ( $this->is_search() ) {
143
- $meta['noindex'] = $this->get_option( 'search_noindex' ) ? 'noindex' : $meta['noindex'];
144
- $meta['nofollow'] = $this->get_option( 'search_nofollow' ) ? 'nofollow' : $meta['nofollow'];
145
- $meta['noarchive'] = $this->get_option( 'search_noarchive' ) ? 'noarchive' : $meta['noarchive'];
146
  }
147
  }
148
 
149
- if ( $this->is_singular() ) {
150
- $meta['noindex'] = $this->get_custom_field( '_genesis_noindex' ) ? 'noindex' : $meta['noindex'];
151
- $meta['nofollow'] = $this->get_custom_field( '_genesis_nofollow' ) ? 'nofollow' : $meta['nofollow'];
152
- $meta['noarchive'] = $this->get_custom_field( '_genesis_noarchive' ) ? 'noarchive' : $meta['noarchive'];
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
- if ( $this->is_protected( $this->get_the_real_ID() ) ) {
155
- $meta['noindex'] = 'noindex';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  }
157
  }
158
 
159
- $post_type = \get_post_type();
160
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
161
- $o = $this->get_option( $this->get_robots_post_type_option_id( $r ) );
162
- if ( ! empty( $o[ $post_type ] ) ) {
163
- $meta[ $r ] = $r;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  }
165
  }
166
 
167
- /**
168
- * Filters the front-end robots array, and strips empty indexes thereafter.
169
- *
170
- * @since 2.6.0
171
- *
172
- * @param array $meta The current term meta.
173
- */
174
- return array_filter( (array) \apply_filters( 'the_seo_framework_robots_meta_array', $meta ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  }
176
 
177
  /**
@@ -181,7 +519,7 @@ class Generate extends User_Data {
181
  *
182
  * @param string $type Accepts 'noindex', 'nofollow', 'noarchive'.
183
  * @param string $post_type The post type, optional. Leave empty to autodetermine type.
184
- * @return bool True if disabled, false otherwise.
185
  */
186
  public function is_post_type_robots_set( $type, $post_type = '' ) {
187
  return isset(
@@ -197,6 +535,7 @@ class Generate extends User_Data {
197
  * @since 2.3.9
198
  * @since 3.1.0 : 1. Removed caching.
199
  * 2. Removed escaping parameter.
 
200
  *
201
  * @param string $type The separator type. Used to fetch option.
202
  * @return string The separator.
@@ -207,8 +546,6 @@ class Generate extends User_Data {
207
 
208
  if ( 'pipe' === $sep_option ) {
209
  $sep = '|';
210
- } elseif ( 'dash' === $sep_option ) {
211
- $sep = '-';
212
  } elseif ( '' !== $sep_option ) {
213
  //* Encapsulate within html entities.
214
  $sep = '&' . $sep_option . ';';
@@ -226,10 +563,12 @@ class Generate extends User_Data {
226
  * @since 2.5.2
227
  * @staticvar string $blogname
228
  *
229
- * @return string $blogname The trimmed and sanitized blogname.
230
  */
231
  public function get_blogname() {
 
232
  static $blogname = null;
 
233
  return isset( $blogname ) ? $blogname : $blogname = trim( \get_bloginfo( 'name', 'display' ) );
234
  }
235
 
@@ -240,18 +579,13 @@ class Generate extends User_Data {
240
  * @since 3.0.0 No longer returns untitled when empty, instead, it just returns an empty string.
241
  * @staticvar string $description
242
  *
243
- * @return string $blogname The trimmed and sanitized blog description.
244
  */
245
  public function get_blogdescription() {
246
 
247
  static $description = null;
248
 
249
- if ( isset( $description ) )
250
- return $description;
251
-
252
- $description = trim( \get_bloginfo( 'description', 'display' ) );
253
-
254
- return $description = $description ?: '';
255
  }
256
 
257
  /**
@@ -260,49 +594,49 @@ class Generate extends User_Data {
260
  *
261
  * @since 2.5.2
262
  *
263
- * @param $match the locale to match. Defaults to WordPress locale.
264
  * @return string Facebook acceptable OG locale.
265
  */
266
  public function fetch_locale( $match = '' ) {
267
 
268
- if ( empty( $match ) )
269
  $match = \get_locale();
270
 
271
- $match_len = strlen( $match );
272
- $valid_locales = (array) $this->fb_locales();
273
- $default = 'en_US';
274
 
275
  if ( $match_len > 5 ) {
276
- //* More than full is used. Make it just full.
277
- $match = substr( $match, 0, 5 );
278
  $match_len = 5;
 
 
279
  }
280
 
281
  if ( 5 === $match_len ) {
282
- //* Full locale is used.
283
 
284
- //* Return the match if found.
285
  if ( in_array( $match, $valid_locales, true ) )
286
  return $match;
287
 
288
- //* Convert to only language portion.
289
- $match = substr( $match, 0, 2 );
290
  $match_len = 2;
 
291
  }
292
 
293
  if ( 2 === $match_len ) {
294
- //* Language key is provided.
295
 
296
  $locale_keys = (array) $this->language_keys();
297
 
298
- //* No need to do for each loop. Just match the keys.
299
- if ( $key = array_search( $match, $locale_keys, true ) ) {
300
- //* Fetch the corresponding value from key within the language array.
 
301
  return $valid_locales[ $key ];
302
  }
303
  }
304
 
305
- return $default;
 
306
  }
307
 
308
  /**
@@ -346,6 +680,7 @@ class Generate extends User_Data {
346
  * @since 2.3.0
347
  * @since 2.7.0 Added output within filter.
348
  * @param string $type The OG type.
 
349
  */
350
  return $type = (string) \apply_filters_ref_array(
351
  'the_seo_framework_ogtype_output',
@@ -360,29 +695,25 @@ class Generate extends User_Data {
360
  * @since 3.1.0
361
  * @TODO use this
362
  * @see get_available_twitter_cards
 
363
  */
364
  public function get_available_open_graph_types() { }
365
 
366
  /**
367
  * Generates the Twitter Card type.
368
  *
369
- * When there's an image found, it will take the said option.
370
- * Otherwise, it will return 'summary' or ''.
371
- *
372
  * @since 2.7.0
373
  * @since 2.8.2 Now considers description output.
374
  * @since 2.9.0 Now listens to $this->get_available_twitter_cards().
375
  * @since 3.1.0 Now inherits filter `the_seo_framework_twittercard_output`.
376
  *
377
- * @return string The Twitter Card type.
378
  */
379
  public function generate_twitter_card_type() {
380
 
381
  $available_cards = $this->get_available_twitter_cards();
382
 
383
- //* No valid Twitter cards have been found.
384
- if ( false === $available_cards )
385
- return '';
386
 
387
  $option = $this->get_option( 'twitter_card' );
388
  $option = trim( \esc_attr( $option ) );
@@ -417,9 +748,11 @@ class Generate extends User_Data {
417
  * Determines which Twitter cards can be used.
418
  *
419
  * @since 2.9.0
 
 
420
  * @staticvar bool|array $cache
421
  *
422
- * @return bool|array False when it shouldn't be used. Array of available cards otherwise.
423
  */
424
  public function get_available_twitter_cards() {
425
 
@@ -428,20 +761,19 @@ class Generate extends User_Data {
428
  if ( isset( $cache ) )
429
  return $cache;
430
 
431
- if ( ! $this->get_twitter_description() || ! $this->get_twitter_title() ) {
432
  $retval = [];
433
  } else {
434
- $retval = $this->get_image_from_cache() ? [ 'summary_large_image', 'summary' ] : [ 'summary' ];
435
  }
436
 
437
  /**
438
- * Filters the available Twitter cards on the front end.
439
  * @since 2.9.0
440
- * @param array $retval Use empty array to invalidate Twitter card.
441
  */
442
  $retval = (array) \apply_filters( 'the_seo_framework_available_twitter_cards', $retval );
443
 
444
- return $cache = $retval ?: false;
445
  }
446
 
447
  /**
@@ -449,21 +781,22 @@ class Generate extends User_Data {
449
  *
450
  * @since 2.6.0
451
  * @since 3.1.0 Is now filterable.
 
452
  *
453
  * @return array Title separators.
454
  */
455
  public function get_separator_list() {
456
  /**
457
  * @since 3.1.0
 
458
  * @param array $list The separator list in { option_name > display_value } format.
459
  * The option name should be translatable within `&...;` tags.
460
- * 'pipe' and 'dash' are excluded from this rule.
461
  */
462
  return (array) \apply_filters(
463
  'the_seo_framework_separator_list',
464
  [
465
  'pipe' => '|',
466
- 'dash' => '-',
467
  'ndash' => '&ndash;',
468
  'mdash' => '&mdash;',
469
  'bull' => '&bull;',
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Generate
4
+ * @subpackage The_SEO_Framework\Getters
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
44
  */
45
  protected function fix_generation_args( &$args ) {
46
  if ( is_array( $args ) ) {
47
+ $args = array_merge(
48
+ [
49
+ 'id' => 0,
50
+ 'taxonomy' => '',
51
+ ],
52
+ $args
53
+ );
54
  } elseif ( is_numeric( $args ) ) {
55
  $args = [
56
  'id' => (int) $args,
73
  * @since 3.1.0 1. Simplified statements, often (not always) speeding things up.
74
  * 2. Now checks for wc_shop and blog types for pagination.
75
  * 3. Removed noydir.
76
+ * @since 4.0.0 1. Now tests for qubit metadata.
77
+ * 2. Added custom query support.
78
+ * 3. Added two parameters.
79
+ * @since 4.0.2 1. Added new copyright directive tags.
80
+ * 2. Now strictly parses the validity of robots directives via a boolean check.
81
  * @global \WP_Query $wp_query
82
  *
83
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
84
+ * @param int <bit> $ignore The ignore level. {
85
+ * 0 = 0b00: Ignore nothing.
86
+ * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
87
+ * 2 = 0b10: Ignore post/term setting. (\The_SEO_Framework\ROBOTS_IGNORE_SETTINGS)
88
+ * 3 = 0b11: Ignore protection and post/term setting.
89
+ * }
90
+ * @return array {
91
+ * string index : string value
92
+ * }
93
  */
94
+ public function robots_meta( $args = null, $ignore = 0b00 ) {
95
+
96
+ if ( null === $args ) {
97
+ $_meta = $this->get_robots_meta_by_query( $ignore );
98
+ } else {
99
+ $this->fix_generation_args( $args );
100
+ $_meta = $this->get_robots_meta_by_args( $args, $ignore );
101
+ }
102
 
 
103
  $meta = [
104
+ 'noindex' => '',
105
+ 'nofollow' => '',
106
+ 'noarchive' => '',
107
+ 'max_snippet_length' => '',
108
+ 'max_image_preview' => '',
109
+ 'max_video_preview' => '',
110
  ];
111
 
112
+ foreach (
113
+ array_intersect_key( $_meta, array_flip( [ 'noindex', 'nofollow', 'noarchive' ] ) )
114
+ as $k => $v
115
+ ) $v and $meta[ $k ] = $k;
116
+
117
+ foreach (
118
+ array_intersect_key( $_meta, array_flip( [ 'max_snippet_length', 'max_image_preview', 'max_video_preview' ] ) )
119
+ as $k => $v
120
+ ) false !== $v and $meta[ $k ] = str_replace( '_', '-', $k ) . "=$v";
121
+
122
+ /**
123
+ * Filters the front-end robots array, and strips empty indexes thereafter.
124
+ *
125
+ * @since 2.6.0
126
+ * @since 4.0.0 Added two parameters ($args and $ignore).
127
+ *
128
+ * @param array $meta The current robots meta.
129
+ * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
130
+ * Is null when query is autodetermined.
131
+ * @param int <bit> $ignore The ignore level. {
132
+ * 0 = 0b00: Ignore nothing.
133
+ * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
134
+ * 2 = 0b10: Ignore post/term setting. (\The_SEO_Framework\ROBOTS_IGNORE_SETTINGS)
135
+ * 3 = 0b11: Ignore protection and post/term setting.
136
+ * }
137
+ */
138
+ return array_filter(
139
+ (array) \apply_filters_ref_array(
140
+ 'the_seo_framework_robots_meta_array',
141
+ [
142
+ $meta,
143
+ $args,
144
+ $ignore,
145
+ ]
146
+ )
147
+ );
148
+ }
149
+
150
+ /**
151
+ * Generates the `noindex`, `nofollow`, `noarchive` robots meta code array from query.
152
+ *
153
+ * @since 4.0.0
154
+ * @since 4.0.2 Added new copyright directive tags.
155
+ * @global \WP_Query $wp_query
156
+ *
157
+ * @param int <bit> $ignore The ignore level. {
158
+ * 0 = 0b00: Ignore nothing.
159
+ * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
160
+ * 2 = 0b10: Ignore post/term setting. (\The_SEO_Framework\ROBOTS_IGNORE_SETTINGS)
161
+ * 3 = 0b11: Ignore protection and post/term setting.
162
+ * }
163
+ * @return array|null robots : {
164
+ * bool 'noindex'
165
+ * bool 'nofollow'
166
+ * bool 'noarchive'
167
+ * false|int <R>=-1> 'max_snippet_length'
168
+ * false|string 'max_image_preview'
169
+ * fasle|int <R>=-1> 'max_video_preview'
170
+ * }
171
+ */
172
+ protected function get_robots_meta_by_query( $ignore = 0b00 ) {
173
+
174
+ $noindex = (bool) $this->get_option( 'site_noindex' );
175
+ $nofollow = (bool) $this->get_option( 'site_nofollow' );
176
+ $noarchive = (bool) $this->get_option( 'site_noarchive' );
177
+
178
+ $max_snippet_length = $max_image_preview = $max_video_preview = false;
179
+
180
+ if ( $this->get_option( 'set_copyright_directives' ) ) {
181
+ $max_snippet_length = $this->get_option( 'max_snippet_length' );
182
+ $max_image_preview = $this->get_option( 'max_image_preview' );
183
+ $max_video_preview = $this->get_option( 'max_video_preview' );
184
+ }
185
+
186
  //* Check homepage SEO settings, set noindex, nofollow and noarchive
187
  if ( $this->is_real_front_page() ) {
188
+ $noindex = $noindex || $this->get_option( 'homepage_noindex' );
189
+ $nofollow = $nofollow || $this->get_option( 'homepage_nofollow' );
190
+ $noarchive = $noarchive || $this->get_option( 'homepage_noarchive' );
191
+
192
+ if ( ! ( $ignore & ROBOTS_IGNORE_PROTECTION ) ) :
193
+ $noindex = $noindex
194
+ || ( $this->get_option( 'home_paged_noindex' ) && ( $this->page() > 1 || $this->paged() > 1 ) );
195
+ endif;
196
  } else {
197
  global $wp_query;
198
 
199
  /**
200
  * Check for 404, or if archive is empty: set noindex for those.
201
  * Don't check this on the homepage. The homepage is sacred in this regard,
202
+ * because page builders and templates can and will take over.
 
203
  *
204
+ * Don't use empty(), null is regarded as indexable.
 
205
  */
206
+ if ( isset( $wp_query->post_count ) && ! $wp_query->post_count )
207
+ $noindex = true;
208
+
209
+ if (
210
+ ! $noindex
211
+ && $this->get_option( 'paged_noindex' )
212
+ && ( $this->is_archive() || $this->is_singular_archive() )
213
+ && $this->paged() > 1
214
+ ) {
215
+ $noindex = true;
216
  }
217
 
218
+ if ( $this->is_archive() ) {
 
 
 
 
 
 
 
 
219
  //* If on custom Taxonomy page, but not a category or tag, then should've received specific term SEO settings.
220
  if ( $this->is_category() ) {
221
+ $noindex = $noindex || $this->get_option( 'category_noindex' );
222
+ $nofollow = $nofollow || $this->get_option( 'category_nofollow' );
223
+ $noarchive = $noarchive || $this->get_option( 'category_noarchive' );
224
  } elseif ( $this->is_tag() ) {
225
+ $noindex = $noindex || $this->get_option( 'tag_noindex' );
226
+ $nofollow = $nofollow || $this->get_option( 'tag_nofollow' );
227
+ $noarchive = $noarchive || $this->get_option( 'tag_noarchive' );
228
  } elseif ( $this->is_author() ) {
229
+ $noindex = $noindex || $this->get_option( 'author_noindex' );
230
+ $nofollow = $nofollow || $this->get_option( 'author_nofollow' );
231
+ $noarchive = $noarchive || $this->get_option( 'author_noarchive' );
232
  } elseif ( $this->is_date() ) {
233
+ $noindex = $noindex || $this->get_option( 'date_noindex' );
234
+ $nofollow = $nofollow || $this->get_option( 'date_nofollow' );
235
+ $noarchive = $noarchive || $this->get_option( 'date_noarchive' );
236
  }
237
  } elseif ( $this->is_search() ) {
238
+ $noindex = $noindex || $this->get_option( 'search_noindex' );
239
+ $nofollow = $nofollow || $this->get_option( 'search_nofollow' );
240
+ $noarchive = $noarchive || $this->get_option( 'search_noarchive' );
241
  }
242
  }
243
 
244
+ if ( $this->is_archive() ) {
245
+ $_post_type_meta = [];
246
+ // Store values from each post type bound to the taxonomy.
247
+ foreach ( $this->get_post_types_from_taxonomy() as $post_type ) {
248
+ foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
249
+ // SECURITY: Put in array to circumvent GLOBALS injection.
250
+ $_post_type_meta[ $r ][] = $this->is_post_type_robots_set( $r, $post_type );
251
+ }
252
+ }
253
+ // Only enable if all post types have the value ticked.
254
+ foreach ( $_post_type_meta as $_type => $_values ) {
255
+ $$_type = $$_type || ! in_array( false, $_values, true );
256
+ }
257
+
258
+ if ( ! ( $ignore & ROBOTS_IGNORE_SETTINGS ) ) :
259
+ $term_meta = $this->get_current_term_meta();
260
 
261
+ foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
262
+ if ( isset( $term_meta[ $r ] ) ) {
263
+ // Test qubit
264
+ $$r = ( $$r | (int) $term_meta[ $r ] ) > .33;
265
+ }
266
+ }
267
+ endif;
268
+ } elseif ( $this->is_singular() ) {
269
+
270
+ $post_type = \get_post_type() ?: $this->get_admin_post_type();
271
+ foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
272
+ $$r = $$r || $this->is_post_type_robots_set( $r, $post_type );
273
+ }
274
+
275
+ if ( ! ( $ignore & ROBOTS_IGNORE_SETTINGS ) ) :
276
+ $post_meta = [
277
+ 'noindex' => $this->get_post_meta_item( '_genesis_noindex' ),
278
+ 'nofollow' => $this->get_post_meta_item( '_genesis_nofollow' ),
279
+ 'noarchive' => $this->get_post_meta_item( '_genesis_noarchive' ),
280
+ ];
281
+
282
+ foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
283
+ // Test qubit
284
+ $$r = ( $$r | (int) $post_meta[ $r ] ) > .33;
285
+ }
286
+ endif;
287
+
288
+ // Overwrite and ignore the user's settings, regardless; unless ignore is set.
289
+ if ( ! ( $ignore & ROBOTS_IGNORE_PROTECTION ) ) :
290
+ if ( $this->is_protected( $this->get_the_real_ID() ) ) {
291
+ $noindex = true;
292
+ }
293
+ endif;
294
+
295
+ /**
296
+ * Noindex on comment pagination.
297
+ * Overwrites and ignores the user's settings, always.
298
+ *
299
+ * N.B. WordPress protects this query variable with options 'page_comments'
300
+ * and 'default_comments_page' via `redirect_canonical()`, so we don't have to.
301
+ * For reference, it fires `remove_query_arg( 'cpage', $redirect['query'] )`;
302
+ */
303
+ if ( (int) \get_query_var( 'cpage', 0 ) > 0 ) {
304
+ $noindex = true;
305
  }
306
  }
307
 
308
+ return compact( 'noindex', 'nofollow', 'noarchive', 'max_snippet_length', 'max_image_preview', 'max_video_preview' );
309
+ }
310
+
311
+ /**
312
+ * Generates the `noindex`, `nofollow`, `noarchive` robots meta code array from arguments.
313
+ *
314
+ * Note that the home-as-blog page can be used for this method.
315
+ *
316
+ * @since 4.0.0
317
+ * @since 4.0.2 Added new copyright directive tags.
318
+ *
319
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
320
+ * @param int <bit> $ignore The ignore level. {
321
+ * 0 = 0b00: Ignore nothing.
322
+ * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
323
+ * 2 = 0b10: Ignore post/term setting. (\The_SEO_Framework\ROBOTS_IGNORE_SETTINGS)
324
+ * 3 = 0b11: Ignore protection and post/term setting.
325
+ * }
326
+ * @return array|null robots : {
327
+ * bool 'noindex'
328
+ * bool 'nofollow'
329
+ * bool 'noarchive'
330
+ * false|int <R>=-1> 'max_snippet_length'
331
+ * false|string 'max_image_preview'
332
+ * fasle|int <R>=-1> 'max_video_preview'
333
+ * }
334
+ */
335
+ protected function get_robots_meta_by_args( $args, $ignore = 0b00 ) {
336
+
337
+ $noindex = (bool) $this->get_option( 'site_noindex' );
338
+ $nofollow = (bool) $this->get_option( 'site_nofollow' );
339
+ $noarchive = (bool) $this->get_option( 'site_noarchive' );
340
+
341
+ $max_snippet_length = $max_image_preview = $max_video_preview = false;
342
+
343
+ if ( $this->get_option( 'set_copyright_directives' ) ) {
344
+ $max_snippet_length = $this->get_option( 'max_snippet_length' );
345
+ $max_image_preview = $this->get_option( 'max_image_preview' );
346
+ $max_video_preview = $this->get_option( 'max_video_preview' );
347
+ }
348
+
349
+ if ( $args['taxonomy'] ) {
350
+ if ( 'category' === $args['taxonomy'] ) {
351
+ $noindex = $noindex || $this->get_option( 'category_noindex' );
352
+ $nofollow = $nofollow || $this->get_option( 'category_nofollow' );
353
+ $noarchive = $noarchive || $this->get_option( 'category_noarchive' );
354
+ } elseif ( 'post_tag' === $args['taxonomy'] ) {
355
+ $noindex = $noindex || $this->get_option( 'tag_noindex' );
356
+ $nofollow = $nofollow || $this->get_option( 'tag_nofollow' );
357
+ $noarchive = $noarchive || $this->get_option( 'tag_noarchive' );
358
+ }
359
+ } else {
360
+ if ( $this->is_real_front_page_by_id( $args['id'] ) ) {
361
+ $noindex = $noindex || $this->get_option( 'homepage_noindex' );
362
+ $nofollow = $nofollow || $this->get_option( 'homepage_nofollow' );
363
+ $noarchive = $noarchive || $this->get_option( 'homepage_noarchive' );
364
  }
365
  }
366
 
367
+ if ( $args['taxonomy'] ) {
368
+ $term = \get_term( $args['id'], $args['taxonomy'] );
369
+ /**
370
+ * Check if archive is empty: set noindex for those.
371
+ */
372
+ if ( empty( $term->count ) )
373
+ $noindex = true;
374
+
375
+ $_post_type_meta = [];
376
+ // Store values from each post type bound to the taxonomy.
377
+ foreach ( $this->get_post_types_from_taxonomy( $args['taxonomy'] ) as $post_type ) {
378
+ foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
379
+ // SECURITY: Put in array to circumvent GLOBALS injection.
380
+ $_post_type_meta[ $r ][] = $this->is_post_type_robots_set( $r, $post_type );
381
+ }
382
+ }
383
+ // Only enable if all post types have the value ticked.
384
+ foreach ( $_post_type_meta as $_type => $_values ) {
385
+ $$_type = $$_type || ! in_array( false, $_values, true );
386
+ }
387
+
388
+ if ( ! ( $ignore & ROBOTS_IGNORE_SETTINGS ) ) :
389
+ $term_meta = $this->get_term_meta( $args['id'] );
390
+
391
+ foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
392
+ if ( isset( $term_meta[ $r ] ) ) {
393
+ // Test qubit
394
+ $$r = ( $$r | (int) $term_meta[ $r ] ) > .33;
395
+ }
396
+ }
397
+ endif;
398
+ } elseif ( $args['id'] ) {
399
+ $post_type = \get_post_type( $args['id'] );
400
+ foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
401
+ $$r = $$r || $this->is_post_type_robots_set( $r, $post_type );
402
+ }
403
+
404
+ if ( ! ( $ignore & ROBOTS_IGNORE_SETTINGS ) ) :
405
+ $post_meta = [
406
+ 'noindex' => $this->get_post_meta_item( '_genesis_noindex', $args['id'] ),
407
+ 'nofollow' => $this->get_post_meta_item( '_genesis_nofollow', $args['id'] ),
408
+ 'noarchive' => $this->get_post_meta_item( '_genesis_noarchive', $args['id'] ),
409
+ ];
410
+
411
+ foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) {
412
+ // Test qubit
413
+ $$r = ( $$r | (int) $post_meta[ $r ] ) > .33;
414
+ }
415
+ endif;
416
+
417
+ // Overwrite and ignore the user's settings, regardless; unless ignore is set.
418
+ if ( ! ( $ignore & ROBOTS_IGNORE_PROTECTION ) ) :
419
+ if ( $this->is_protected( $args['id'] ) ) {
420
+ $noindex = true;
421
+ }
422
+ endif;
423
+ }
424
+
425
+ return compact( 'noindex', 'nofollow', 'noarchive', 'max_snippet_length', 'max_image_preview', 'max_video_preview' );
426
+ }
427
+
428
+ /**
429
+ * Generates the `noindex` robots meta code array from arguments.
430
+ *
431
+ * This method is tailor-made for everything that relies on the noindex-state, as it's
432
+ * a very controlling and powerful feature.
433
+ *
434
+ * Note that the home-as-blog page can be used for this method.
435
+ *
436
+ * @since 4.0.0
437
+ *
438
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
439
+ * @param int <bit> $ignore The ignore level. {
440
+ * 0 = 0b00: Ignore nothing.
441
+ * 1 = 0b01: Ignore protection. (\The_SEO_Framework\ROBOTS_IGNORE_PROTECTION)
442
+ * 2 = 0b10: Ignore post/term setting. (\The_SEO_Framework\ROBOTS_IGNORE_SETTINGS)
443
+ * 3 = 0b11: Ignore protection and post/term setting.
444
+ * }
445
+ * @return bool Whether noindex is set or not
446
+ */
447
+ public function is_robots_meta_noindex_set_by_args( $args, $ignore = 0b00 ) {
448
+
449
+ $this->fix_generation_args( $args );
450
+
451
+ $noindex = (bool) $this->get_option( 'site_noindex' );
452
+
453
+ if ( $args['taxonomy'] ) {
454
+ if ( 'category' === $args['taxonomy'] ) {
455
+ $noindex = $noindex || $this->get_option( 'category_noindex' );
456
+ } elseif ( 'post_tag' === $args['taxonomy'] ) {
457
+ $noindex = $noindex || $this->get_option( 'tag_noindex' );
458
+ }
459
+ } else {
460
+ if ( $this->is_real_front_page_by_id( $args['id'] ) ) {
461
+ $noindex = $noindex || $this->get_option( 'homepage_noindex' );
462
+ }
463
+ }
464
+
465
+ if ( $args['taxonomy'] ) {
466
+ $term = \get_term( $args['id'], $args['taxonomy'] );
467
+ /**
468
+ * Check if archive is empty: set noindex for those.
469
+ */
470
+ if ( empty( $term->count ) )
471
+ $noindex = true;
472
+
473
+ $_post_type_meta = [];
474
+ // Store values from each post type bound to the taxonomy.
475
+ foreach ( $this->get_post_types_from_taxonomy( $args['taxonomy'] ) as $post_type ) {
476
+ // SECURITY: Put in array to circumvent GLOBALS injection.
477
+ $_post_type_meta['noindex'][] = $this->is_post_type_robots_set( 'noindex', $post_type );
478
+ }
479
+ // Only enable if all post types have the value ticked.
480
+ foreach ( $_post_type_meta as $_type => $_values ) {
481
+ $$_type = $$_type || ! in_array( false, $_values, true );
482
+ }
483
+
484
+ if ( ! ( $ignore & ROBOTS_IGNORE_SETTINGS ) ) :
485
+ $term_meta = $this->get_term_meta( $args['id'] );
486
+
487
+ if ( isset( $term_meta['noindex'] ) ) {
488
+ // Test qubit
489
+ $noindex = ( $noindex | (int) $term_meta['noindex'] ) > .33;
490
+ }
491
+ endif;
492
+ } elseif ( $args['id'] ) {
493
+ $post_type = \get_post_type( $args['id'] );
494
+ $noindex = $noindex || $this->is_post_type_robots_set( 'noindex', $post_type );
495
+
496
+ if ( ! ( $ignore & ROBOTS_IGNORE_SETTINGS ) ) :
497
+ $post_meta = [
498
+ 'noindex' => $this->get_post_meta_item( '_genesis_noindex', $args['id'] ),
499
+ ];
500
+ // Test qubit
501
+ $noindex = ( $noindex | (int) $post_meta['noindex'] ) > .33;
502
+ endif;
503
+
504
+ // Overwrite and ignore the user's settings, regardless; unless ignore is set.
505
+ if ( ! ( $ignore & ROBOTS_IGNORE_PROTECTION ) ) :
506
+ if ( $this->is_protected( $args['id'] ) ) {
507
+ $noindex = true;
508
+ }
509
+ endif;
510
+ }
511
+
512
+ return $noindex;
513
  }
514
 
515
  /**
519
  *
520
  * @param string $type Accepts 'noindex', 'nofollow', 'noarchive'.
521
  * @param string $post_type The post type, optional. Leave empty to autodetermine type.
522
+ * @return bool True if noindex, nofollow, or noarchive is set; false otherwise.
523
  */
524
  public function is_post_type_robots_set( $type, $post_type = '' ) {
525
  return isset(
535
  * @since 2.3.9
536
  * @since 3.1.0 : 1. Removed caching.
537
  * 2. Removed escaping parameter.
538
+ * @since 4.0.0 No longer converts the `dash` separator option.
539
  *
540
  * @param string $type The separator type. Used to fetch option.
541
  * @return string The separator.
546
 
547
  if ( 'pipe' === $sep_option ) {
548
  $sep = '|';
 
 
549
  } elseif ( '' !== $sep_option ) {
550
  //* Encapsulate within html entities.
551
  $sep = '&' . $sep_option . ';';
563
  * @since 2.5.2
564
  * @staticvar string $blogname
565
  *
566
+ * @return string $blogname The escaped and sanitized blogname.
567
  */
568
  public function get_blogname() {
569
+
570
  static $blogname = null;
571
+
572
  return isset( $blogname ) ? $blogname : $blogname = trim( \get_bloginfo( 'name', 'display' ) );
573
  }
574
 
579
  * @since 3.0.0 No longer returns untitled when empty, instead, it just returns an empty string.
580
  * @staticvar string $description
581
  *
582
+ * @return string $blogname The escaped and sanitized blog description.
583
  */
584
  public function get_blogdescription() {
585
 
586
  static $description = null;
587
 
588
+ return isset( $description ) ? $description : $description = trim( \get_bloginfo( 'description', 'display' ) );
 
 
 
 
 
589
  }
590
 
591
  /**
594
  *
595
  * @since 2.5.2
596
  *
597
+ * @param string $match the locale to match. Defaults to WordPress locale.
598
  * @return string Facebook acceptable OG locale.
599
  */
600
  public function fetch_locale( $match = '' ) {
601
 
602
+ if ( ! $match )
603
  $match = \get_locale();
604
 
605
+ $match_len = strlen( $match );
606
+ $valid_locales = $this->fb_locales();
 
607
 
608
  if ( $match_len > 5 ) {
 
 
609
  $match_len = 5;
610
+ // More than standard-full locale ID is used. Make it just full.
611
+ $match = substr( $match, 0, $match_len );
612
  }
613
 
614
  if ( 5 === $match_len ) {
615
+ // Full locale is used.
616
 
 
617
  if ( in_array( $match, $valid_locales, true ) )
618
  return $match;
619
 
620
+ // Convert to only language portion.
 
621
  $match_len = 2;
622
+ $match = substr( $match, 0, $match_len );
623
  }
624
 
625
  if ( 2 === $match_len ) {
626
+ // Only a language key is provided.
627
 
628
  $locale_keys = (array) $this->language_keys();
629
 
630
+ // Find first matching key.
631
+ $key = array_search( $match, $locale_keys, true );
632
+
633
+ if ( $key ) {
634
  return $valid_locales[ $key ];
635
  }
636
  }
637
 
638
+ // Return default locale.
639
+ return 'en_US';
640
  }
641
 
642
  /**
680
  * @since 2.3.0
681
  * @since 2.7.0 Added output within filter.
682
  * @param string $type The OG type.
683
+ * @param int $id The page/term/object ID.
684
  */
685
  return $type = (string) \apply_filters_ref_array(
686
  'the_seo_framework_ogtype_output',
695
  * @since 3.1.0
696
  * @TODO use this
697
  * @see get_available_twitter_cards
698
+ * @ignore
699
  */
700
  public function get_available_open_graph_types() { }
701
 
702
  /**
703
  * Generates the Twitter Card type.
704
  *
 
 
 
705
  * @since 2.7.0
706
  * @since 2.8.2 Now considers description output.
707
  * @since 2.9.0 Now listens to $this->get_available_twitter_cards().
708
  * @since 3.1.0 Now inherits filter `the_seo_framework_twittercard_output`.
709
  *
710
+ * @return string The Twitter Card type. When no social title is found, an empty string will be returned.
711
  */
712
  public function generate_twitter_card_type() {
713
 
714
  $available_cards = $this->get_available_twitter_cards();
715
 
716
+ if ( ! $available_cards ) return '';
 
 
717
 
718
  $option = $this->get_option( 'twitter_card' );
719
  $option = trim( \esc_attr( $option ) );
748
  * Determines which Twitter cards can be used.
749
  *
750
  * @since 2.9.0
751
+ * @since 4.0.0 1. Now only asserts the social titles as required.
752
+ * 2. Now always returns an array, instead of a boolean (false) on failure.
753
  * @staticvar bool|array $cache
754
  *
755
+ * @return array False when it shouldn't be used. Array of available cards otherwise.
756
  */
757
  public function get_available_twitter_cards() {
758
 
761
  if ( isset( $cache ) )
762
  return $cache;
763
 
764
+ if ( ! $this->get_twitter_title() ) {
765
  $retval = [];
766
  } else {
767
+ $retval = [ 'summary_large_image', 'summary' ];
768
  }
769
 
770
  /**
 
771
  * @since 2.9.0
772
+ * @param array $retval The available Twitter cards. Use empty array to invalidate Twitter card.
773
  */
774
  $retval = (array) \apply_filters( 'the_seo_framework_available_twitter_cards', $retval );
775
 
776
+ return $cache = $retval ?: [];
777
  }
778
 
779
  /**
781
  *
782
  * @since 2.6.0
783
  * @since 3.1.0 Is now filterable.
784
+ * @since 4.0.0 Removed the dash key.
785
  *
786
  * @return array Title separators.
787
  */
788
  public function get_separator_list() {
789
  /**
790
  * @since 3.1.0
791
+ * @since 4.0.0 Removed the dash key.
792
  * @param array $list The separator list in { option_name > display_value } format.
793
  * The option name should be translatable within `&...;` tags.
794
+ * 'pipe' is excluded from this rule.
795
  */
796
  return (array) \apply_filters(
797
  'the_seo_framework_separator_list',
798
  [
799
  'pipe' => '|',
 
800
  'ndash' => '&ndash;',
801
  'mdash' => '&mdash;',
802
  'bull' => '&bull;',
inc/classes/index.php CHANGED
@@ -8,8 +8,6 @@
8
  * - Deprecated
9
  * |-> Final
10
  * - Debug
11
- * |-> Interface:
12
- * - Debug_Interface
13
  * |-> Final
14
  *
15
  * ## Failsafe:
@@ -18,7 +16,6 @@
18
  *
19
  * ## Façade (bottom is called first):
20
  * - | Core
21
- * | Compat
22
  * | Query
23
  * | Init
24
  * | Admin_Init
@@ -33,20 +30,13 @@
33
  * | Generate_Url
34
  * | Generate_Image
35
  * | Generate_Ldjson
36
- * | Doing_It_Right
37
  * | Profile
38
- * | Inpost
39
  * | Admin_Pages
40
  * | Sanitize
41
  * | Site_Options
42
- * | Metaboxes
43
- * | Sitemaps
44
  * | Cache
45
  * | Feed
46
  * | Load
47
- * |-> Interface:
48
- * - Debug_Interface
49
  * |-> Final
50
- * |-> Instance
51
- *
52
  */
8
  * - Deprecated
9
  * |-> Final
10
  * - Debug
 
 
11
  * |-> Final
12
  *
13
  * ## Failsafe:
16
  *
17
  * ## Façade (bottom is called first):
18
  * - | Core
 
19
  * | Query
20
  * | Init
21
  * | Admin_Init
30
  * | Generate_Url
31
  * | Generate_Image
32
  * | Generate_Ldjson
 
33
  * | Profile
 
34
  * | Admin_Pages
35
  * | Sanitize
36
  * | Site_Options
 
 
37
  * | Cache
38
  * | Feed
39
  * | Load
 
 
40
  * |-> Final
41
+ * |-> Instanced in function `the_seo_framework()`
 
42
  */
inc/classes/init.class.php CHANGED
@@ -1,7 +1,8 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -32,15 +33,6 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
32
  */
33
  class Init extends Query {
34
 
35
- /**
36
- * Allow object caching through a filter.
37
- *
38
- * @since 2.4.3
39
- *
40
- * @var bool Enable object caching.
41
- */
42
- protected $use_object_cache = true;
43
-
44
  /**
45
  * A true legacy. Ran the plugin on the front-end.
46
  *
@@ -61,15 +53,15 @@ class Init extends Query {
61
  public function init_the_seo_framework() {
62
 
63
  /**
64
- * Runs before the plugin is initialized.
65
  * @since 2.8.0
 
66
  */
67
  \do_action( 'the_seo_framework_init' );
68
 
69
  $this->init_global_actions();
70
  $this->init_global_filters();
71
 
72
- if ( $this->is_admin() ) {
73
  $this->init_admin_actions();
74
  } else {
75
  $this->init_front_end_actions();
@@ -77,9 +69,9 @@ class Init extends Query {
77
  }
78
 
79
  /**
 
80
  * Runs after the plugin is initialized.
81
  * Use this to remove filters and actions.
82
- * @since 3.1.0
83
  */
84
  \do_action( 'the_seo_framework_after_init' );
85
  }
@@ -91,15 +83,8 @@ class Init extends Query {
91
  */
92
  public function init_global_actions() {
93
 
94
- if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
95
  $this->init_cron_actions();
96
- }
97
-
98
- //* Add query strings for sitemap rewrite.
99
- \add_action( 'init', [ $this, 'rewrite_rule_sitemap' ], 1 );
100
-
101
- //* Enqueue sitemap rewrite flush
102
- \add_action( 'shutdown', [ $this, 'maybe_flush_rewrite' ], 999 );
103
  }
104
 
105
  /**
@@ -108,10 +93,6 @@ class Init extends Query {
108
  * @since 2.8.0
109
  */
110
  public function init_global_filters() {
111
-
112
- //* Add query strings for sitemap rewrite.
113
- \add_filter( 'query_vars', [ $this, 'enqueue_sitemap_query_vars' ], 1, 1 );
114
-
115
  //* Adjust category link to accommodate primary term.
116
  \add_filter( 'post_link_category', [ $this, '_adjust_post_link_category' ], 10, 3 );
117
  }
@@ -122,10 +103,12 @@ class Init extends Query {
122
  * @since 2.8.0
123
  */
124
  public function init_cron_actions() {
125
-
126
- //* Flush post cache.
127
  $this->init_post_cache_actions();
128
 
 
 
 
129
  }
130
 
131
  /**
@@ -136,23 +119,23 @@ class Init extends Query {
136
  public function init_admin_actions() {
137
 
138
  /**
139
- * Runs before the plugin is initialized in the admin screens.
140
  * @since 2.8.0
 
141
  */
142
  \do_action( 'the_seo_framework_admin_init' );
143
 
144
- //* Initialize caching actions.
145
  $this->init_admin_caching_actions();
146
 
147
  //= Initialize profile fields.
148
  $this->init_profile_fields();
149
 
150
  //= Initialize term meta filters and actions.
151
- $this->initialize_term_meta();
152
 
153
  //* Save post data.
154
- \add_action( 'save_post', [ $this, 'inpost_seo_save' ], 1, 2 );
155
- \add_action( 'edit_attachment', [ $this, 'inattachment_seo_save' ], 1 );
156
  \add_action( 'save_post', [ $this, '_save_inpost_primary_term' ], 1, 2 );
157
 
158
  //* Enqueues admin scripts.
@@ -162,58 +145,48 @@ class Init extends Query {
162
  \add_filter( 'plugin_action_links_' . THE_SEO_FRAMEWORK_PLUGIN_BASENAME, [ $this, '_add_plugin_action_links' ], 10, 2 );
163
  \add_filter( 'plugin_row_meta', [ $this, '_add_plugin_row_meta' ], 10, 2 );
164
 
165
- //* Initialize post states.
166
- \add_action( 'current_screen', [ $this, 'post_state' ] );
 
167
 
168
- if ( $this->get_option( 'display_seo_bar_tables' ) ) {
169
- //* Initialize columns.
170
- \add_action( 'current_screen', [ $this, 'init_columns' ] );
171
 
172
- //* Ajax handlers for columns.
173
- \add_action( 'wp_ajax_add-tag', [ $this, '_init_columns_wp_ajax_add_tag' ], -1 );
174
- \add_action( 'wp_ajax_inline-save', [ $this, '_init_columns_wp_ajax_inline_save' ], -1 );
175
- \add_action( 'wp_ajax_inline-save-tax', [ $this, '_init_columns_wp_ajax_inline_save_tax' ], -1 );
176
- }
177
-
178
- if ( $this->load_options ) :
179
- // Enqueue i18n defaults.
180
- \add_action( 'admin_init', [ $this, 'enqueue_page_defaults' ], 1 );
181
 
182
- //* Set up site settings and save/reset them
183
- \add_action( 'admin_init', [ $this, 'register_settings' ], 5 );
184
 
185
- //* Load the SEO admin page content and handlers.
186
- \add_action( 'admin_init', [ $this, 'settings_init' ], 10 );
187
 
188
- //* Enqueue Inpost meta boxes.
189
- \add_action( 'add_meta_boxes', [ $this, 'add_inpost_seo_box_init' ], 5 );
190
 
191
- //* Enqueue Taxonomy meta output.
192
- \add_action( 'current_screen', [ $this, 'add_taxonomy_seo_box_init' ], 10 );
193
 
194
- // Add menu links and register $this->seo_settings_page_hook
195
  \add_action( 'admin_menu', [ $this, 'add_menu_link' ] );
196
 
197
- // Set up notices
198
  \add_action( 'admin_notices', [ $this, 'notices' ] );
199
 
200
- // Load nessecary assets
201
- \add_action( 'admin_init', [ $this, 'load_assets' ] );
202
-
203
  //* Admin AJAX for counter options.
204
  \add_action( 'wp_ajax_the_seo_framework_update_counter', [ $this, '_wp_ajax_update_counter_type' ] );
205
 
 
 
 
206
  //* Admin AJAX for TSF Cropper
207
  \add_action( 'wp_ajax_tsf-crop-image', [ $this, '_wp_ajax_crop_image' ] );
208
-
209
- // Add extra removable query arguments to the list.
210
- \add_filter( 'removable_query_args', [ $this, 'add_removable_query_args' ] );
211
  endif;
212
 
213
  /**
 
214
  * Runs after the plugin is initialized in the admin screens.
215
  * Use this to remove actions.
216
- * @since 2.9.4
217
  */
218
  \do_action( 'the_seo_framework_after_admin_init' );
219
  }
@@ -229,8 +202,8 @@ class Init extends Query {
229
  protected function init_front_end_actions() {
230
 
231
  /**
232
- * Runs before the plugin is initialized on the front-end.
233
  * @since 2.8.0
 
234
  */
235
  \do_action( 'the_seo_framework_front_init' );
236
 
@@ -246,25 +219,15 @@ class Init extends Query {
246
  //* Earlier removal of the generator tag. Doesn't require filter.
247
  \remove_action( 'wp_head', 'wp_generator' );
248
 
249
- //* Adds site icon tags to the sitemap stylesheet.
250
- \add_action( 'the_seo_framework_xsl_head', 'wp_site_icon', 99 );
251
-
252
- /**
253
- * Outputs sitemap or stylesheet on request.
254
- *
255
- * Adding a higher priority will cause a trailing slash to be added.
256
- * We need to be in front of the queue to prevent this from happening.
257
- *
258
- * This brings other issues we had to fix. @see $this->validate_sitemap_scheme()
259
- */
260
- \add_action( 'template_redirect', [ $this, 'maybe_output_sitemap' ], 1 );
261
- \add_action( 'template_redirect', [ $this, 'maybe_output_sitemap_stylesheet' ], 1 );
262
 
263
  //* Initialize 301 redirects.
264
  \add_action( 'template_redirect', [ $this, '_init_custom_field_redirect' ] );
265
 
266
- //* Initialize feed alteration.
267
- \add_action( 'template_redirect', [ $this, '_init_feed_output' ] );
268
 
269
  //* Output meta tags.
270
  \add_action( 'wp_head', [ $this, 'html_output' ], 1 );
@@ -275,10 +238,17 @@ class Init extends Query {
275
  if ( $this->get_option( 'alter_search_query' ) )
276
  $this->init_alter_search_query();
277
 
 
 
 
 
 
 
 
278
  /**
 
279
  * Runs before the plugin is initialized on the front-end.
280
  * Use this to remove actions.
281
- * @since 2.9.4
282
  */
283
  \do_action( 'the_seo_framework_after_front_init' );
284
  }
@@ -297,9 +267,7 @@ class Init extends Query {
297
  * @since 2.9.3
298
  * @param bool $overwrite_titles Whether to enable title overwriting.
299
  */
300
- $overwrite_titles = \apply_filters( 'the_seo_framework_overwrite_titles', true );
301
-
302
- if ( $overwrite_titles ) {
303
  //* Removes all pre_get_document_title filters.
304
  \remove_all_filters( 'pre_get_document_title', false );
305
 
@@ -310,21 +278,32 @@ class Init extends Query {
310
 
311
  /**
312
  * @since 2.4.1
313
- * @param bool $overwrite_titles Whether to enable title overwriting.
314
  */
315
  if ( \apply_filters( 'the_seo_framework_manipulate_title', true ) ) {
316
  \remove_all_filters( 'wp_title', false );
317
  //* Override WordPress Title
318
- \add_filter( 'wp_title', [ $this, 'get_wp_title' ], 9, 3 );
319
  }
320
  }
 
 
 
 
 
 
 
 
 
 
 
 
321
  }
322
 
323
  /**
324
  * Runs header actions.
325
  *
326
  * @since 3.1.0
327
- * @uses $this->call_function()
328
  *
329
  * @param string $location Either 'before' or 'after'.
330
  * @return string The filter output.
@@ -343,12 +322,10 @@ class Init extends Query {
343
  $functions = (array) \apply_filters( "the_seo_framework_{$location}_output", [] );
344
 
345
  foreach ( $functions as $function ) {
346
- if ( isset( $function['callback'] ) ) {
347
- $output .= $this->call_function(
348
- $function['callback'],
349
- '3.1.0',
350
- isset( $function['args'] ) ? $function['args'] : ''
351
- );
352
  }
353
  }
354
 
@@ -363,10 +340,12 @@ class Init extends Query {
363
  * @since 3.0.0 Now converts timezone if needed.
364
  * @since 3.1.0 1. Now no longer outputs anything on preview.
365
  * 2. Now no longer outputs anything on blocked post types.
 
 
366
  */
367
  public function html_output() {
368
 
369
- if ( $this->is_preview() || $this->is_post_type_disabled() ) return;
370
 
371
  /**
372
  * @since 2.6.0
@@ -394,9 +373,8 @@ class Init extends Query {
394
  $robots = $this->robots();
395
 
396
  /**
397
- * Adds content before the output and caches it through Object caching.
398
  * @since 2.6.0
399
- * @param string $before The content before the SEO output.
400
  */
401
  $before = (string) \apply_filters( 'the_seo_framework_pre', '' );
402
 
@@ -459,9 +437,8 @@ class Init extends Query {
459
  $after_legacy = $this->get_legacy_header_filters_output( 'after' );
460
 
461
  /**
462
- * Adds content after the output and caches it through Object caching.
463
  * @since 2.6.0
464
- * @param string $after The content after the SEO output.
465
  */
466
  $after = (string) \apply_filters( 'the_seo_framework_pro', '' );
467
 
@@ -470,11 +447,8 @@ class Init extends Query {
470
  $this->use_object_cache and $this->object_cache_set( $cache_key, $output, DAY_IN_SECONDS );
471
  endif;
472
 
473
- $output = $this->get_plugin_indicator( 'before' )
474
- . $output
475
- . $this->get_plugin_indicator( 'after', $init_start );
476
-
477
- echo PHP_EOL . $output . PHP_EOL; // xss ok
478
 
479
  /**
480
  * @since 2.6.0
@@ -486,18 +460,32 @@ class Init extends Query {
486
  * Redirects singular page to an alternate URL.
487
  *
488
  * @since 2.9.0
489
- * @since 3.1.0 1. Now no longer redirects on preview.
490
- * 2. Now listens to post type settings.
 
 
 
491
  * @access private
492
  *
493
  * @return void early on non-singular pages.
494
  */
495
  public function _init_custom_field_redirect() {
496
 
497
- if ( ! $this->is_singular() || $this->is_preview() || $this->is_post_type_disabled() )
498
- return;
 
 
 
 
 
 
 
 
 
 
 
 
499
 
500
- $url = $this->get_custom_field( 'redirect' );
501
  $url and $this->do_redirect( $url );
502
  }
503
 
@@ -538,16 +526,28 @@ class Init extends Query {
538
  $path = $this->set_url_scheme( $url, 'relative' );
539
  $url = \trailingslashit( $this->get_home_host() ) . ltrim( $path, ' /' );
540
 
 
541
  $scheme = $this->is_ssl() ? 'https' : 'http';
542
 
543
  \wp_safe_redirect( $this->set_url_scheme( $url, $scheme ), $redirect_type );
544
  exit;
545
  }
546
 
547
- \wp_redirect( $url, $redirect_type ); // phpcs:ignore -- intended feature. Disable via $this->allow_external_redirect().
 
548
  exit;
549
  }
550
 
 
 
 
 
 
 
 
 
 
 
551
  /**
552
  * Edits the robots.txt output.
553
  * Requires not to have a robots.txt file in the root directory.
@@ -575,7 +575,7 @@ class Init extends Query {
575
 
576
  if ( $this->use_object_cache ) {
577
  $cache_key = $this->get_robots_txt_cache_key();
578
- $output = $this->object_cache_get( $cache_key );
579
  } else {
580
  $output = false;
581
  }
@@ -583,22 +583,18 @@ class Init extends Query {
583
  if ( false === $output ) :
584
  $output = '';
585
 
586
- $parsed_home_url = \wp_parse_url( rtrim( \get_home_url(), ' /\\' ) );
587
- $home_path = ! empty( $parsed_home_url['path'] ) ? \esc_attr( $parsed_home_url['path'] ) : '';
588
-
589
- if ( $this->is_subdirectory_installation() || $home_path ) {
590
  $output .= '# This is an invalid robots.txt location.' . "\r\n";
591
  $output .= '# Please visit: ' . \esc_url( \trailingslashit( $this->set_preferred_url_scheme( $this->get_home_host() ) ) . 'robots.txt' ) . "\r\n";
592
  $output .= "\r\n";
593
  }
594
 
595
- $site_url = \wp_parse_url( \site_url() );
596
- $site_path = ( ! empty( $site_url['path'] ) ) ? \esc_attr( $site_url['path'] ) : '';
597
 
598
  /**
599
- * Don't forget to add line breaks ( "\r\n" || PHP_EOL )
600
  * @since 2.5.0
601
  * @param string $pre The output before this plugin's output.
 
602
  */
603
  $output .= (string) \apply_filters( 'the_seo_framework_robots_txt_pre', '' );
604
 
@@ -612,42 +608,84 @@ class Init extends Query {
612
  * @param bool $disallow Whether to disallow robots queries.
613
  */
614
  if ( \apply_filters( 'the_seo_framework_robots_disallow_queries', false ) ) {
615
- $output .= "Disallow: $home_path/*?*\r\n";
616
  }
617
 
618
  /**
619
- * Don't forget to add line breaks ( "\r\n" || PHP_EOL )
620
  * @since 2.5.0
621
  * @param string $pro The output after this plugin's output.
 
622
  */
623
  $output .= (string) \apply_filters( 'the_seo_framework_robots_txt_pro', '' );
624
 
625
  //* Add extra whitespace and sitemap full URL
626
- if ( $this->can_do_sitemap_robots( true ) )
627
- $output .= "\r\nSitemap: " . \esc_url( $this->get_sitemap_xml_url() ) . "\r\n";
 
 
 
 
 
 
 
628
 
629
  $this->use_object_cache and $this->object_cache_set( $cache_key, $output, 86400 );
630
  endif;
631
 
632
- /**
633
- * Completely override robots with output.
634
- * @since 2.5.0
635
- */
636
  $robots_txt = $output;
637
 
638
  return $robots_txt;
639
  }
640
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
641
  /**
642
  * Initializes search query adjustments.
643
  *
644
  * @since 2.9.4
645
  */
646
  public function init_alter_search_query() {
647
-
648
- $type = $this->get_option( 'alter_search_query_type' );
649
-
650
- switch ( $type ) :
651
  case 'post_query':
652
  \add_filter( 'the_posts', [ $this, '_alter_search_query_post' ], 10, 2 );
653
  break;
@@ -665,10 +703,7 @@ class Init extends Query {
665
  * @since 2.9.4
666
  */
667
  public function init_alter_archive_query() {
668
-
669
- $type = $this->get_option( 'alter_archive_query_type' );
670
-
671
- switch ( $type ) :
672
  case 'post_query':
673
  \add_filter( 'the_posts', [ $this, '_alter_archive_query_post' ], 10, 2 );
674
  break;
@@ -757,7 +792,7 @@ class Init extends Query {
757
  * @since 2.9.4
758
  * @access private
759
  *
760
- * @param array $posts The array of retrieved posts.
761
  * @param \WP_Query $wp_query The WP_Query instance.
762
  * @return array $posts
763
  */
@@ -768,7 +803,7 @@ class Init extends Query {
768
  return $posts;
769
 
770
  foreach ( $posts as $n => $post ) {
771
- if ( $this->get_custom_field( 'exclude_local_search', $post->ID ) ) {
772
  unset( $posts[ $n ] );
773
  }
774
  }
@@ -785,7 +820,7 @@ class Init extends Query {
785
  * @since 2.9.4
786
  * @access private
787
  *
788
- * @param array $posts The array of retrieved posts.
789
  * @param \WP_Query $wp_query The WP_Query instance.
790
  * @return array $posts
791
  */
@@ -796,7 +831,7 @@ class Init extends Query {
796
  return $posts;
797
 
798
  foreach ( $posts as $n => $post ) {
799
- if ( $this->get_custom_field( 'exclude_from_archive', $post->ID ) ) {
800
  unset( $posts[ $n ] );
801
  }
802
  }
@@ -813,10 +848,10 @@ class Init extends Query {
813
  * @since 2.9.4
814
  * @since 3.1.0 Now checks for the post type.
815
  *
816
- * @param \WP_Query $wp_query WP_Query object. Passed by reference for performance.
817
  * @return bool
818
  */
819
- protected function is_archive_query_adjustment_blocked( &$wp_query ) {
820
 
821
  static $has_filter = null;
822
 
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Init
4
  */
5
+
6
  namespace The_SEO_Framework;
7
 
8
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
33
  */
34
  class Init extends Query {
35
 
 
 
 
 
 
 
 
 
 
36
  /**
37
  * A true legacy. Ran the plugin on the front-end.
38
  *
53
  public function init_the_seo_framework() {
54
 
55
  /**
 
56
  * @since 2.8.0
57
+ * Runs before the plugin is initialized.
58
  */
59
  \do_action( 'the_seo_framework_init' );
60
 
61
  $this->init_global_actions();
62
  $this->init_global_filters();
63
 
64
+ if ( \is_admin() ) {
65
  $this->init_admin_actions();
66
  } else {
67
  $this->init_front_end_actions();
69
  }
70
 
71
  /**
72
+ * @since 3.1.0
73
  * Runs after the plugin is initialized.
74
  * Use this to remove filters and actions.
 
75
  */
76
  \do_action( 'the_seo_framework_after_init' );
77
  }
83
  */
84
  public function init_global_actions() {
85
 
86
+ if ( \wp_doing_cron() )
87
  $this->init_cron_actions();
 
 
 
 
 
 
 
88
  }
89
 
90
  /**
93
  * @since 2.8.0
94
  */
95
  public function init_global_filters() {
 
 
 
 
96
  //* Adjust category link to accommodate primary term.
97
  \add_filter( 'post_link_category', [ $this, '_adjust_post_link_category' ], 10, 3 );
98
  }
103
  * @since 2.8.0
104
  */
105
  public function init_cron_actions() {
106
+ //* Init post update/delete caching actions.
 
107
  $this->init_post_cache_actions();
108
 
109
+ //* Ping searchengines.
110
+ if ( $this->get_option( 'ping_use_cron' ) )
111
+ \add_action( 'tsf_sitemap_cron_hook', Bridges\Ping::class . '::ping_search_engines' );
112
  }
113
 
114
  /**
119
  public function init_admin_actions() {
120
 
121
  /**
 
122
  * @since 2.8.0
123
+ * Runs before the plugin is initialized in the admin screens.
124
  */
125
  \do_action( 'the_seo_framework_admin_init' );
126
 
127
+ //= Initialize caching actions.
128
  $this->init_admin_caching_actions();
129
 
130
  //= Initialize profile fields.
131
  $this->init_profile_fields();
132
 
133
  //= Initialize term meta filters and actions.
134
+ $this->init_term_meta();
135
 
136
  //* Save post data.
137
+ \add_action( 'save_post', [ $this, '_update_post_meta' ], 1, 2 );
138
+ \add_action( 'edit_attachment', [ $this, '_update_attachment_meta' ], 1 );
139
  \add_action( 'save_post', [ $this, '_save_inpost_primary_term' ], 1, 2 );
140
 
141
  //* Enqueues admin scripts.
145
  \add_filter( 'plugin_action_links_' . THE_SEO_FRAMEWORK_PLUGIN_BASENAME, [ $this, '_add_plugin_action_links' ], 10, 2 );
146
  \add_filter( 'plugin_row_meta', [ $this, '_add_plugin_row_meta' ], 10, 2 );
147
 
148
+ if ( $this->load_options ) :
149
+ //* Set up site settings and allow saving resetting them.
150
+ \add_action( 'admin_init', [ $this, 'register_settings' ], 5 );
151
 
152
+ //* Initialize the SEO Bar for tables.
153
+ \add_action( 'admin_init', [ $this, '_init_seo_bar_tables' ] );
 
154
 
155
+ //* Initialize List Edit for tables.
156
+ \add_action( 'admin_init', [ $this, '_init_list_edit' ] );
 
 
 
 
 
 
 
157
 
158
+ //* Adds post states to list view tables.
159
+ \add_filter( 'display_post_states', [ $this, '_add_post_state' ], 10, 2 );
160
 
161
+ //* Loads setting notices.
162
+ \add_action( 'the_seo_framework_setting_notices', [ $this, '_do_settings_page_notices' ] );
163
 
164
+ //* Enqueue Post meta boxes.
165
+ \add_action( 'add_meta_boxes', [ $this, '_init_post_edit_view' ], 5, 2 );
166
 
167
+ //* Enqueue Term meta output.
168
+ \add_action( 'current_screen', [ $this, '_init_term_edit_view' ] );
169
 
170
+ //* Add menu links and register $this->seo_settings_page_hook
171
  \add_action( 'admin_menu', [ $this, 'add_menu_link' ] );
172
 
173
+ //* Set up notices
174
  \add_action( 'admin_notices', [ $this, 'notices' ] );
175
 
 
 
 
176
  //* Admin AJAX for counter options.
177
  \add_action( 'wp_ajax_the_seo_framework_update_counter', [ $this, '_wp_ajax_update_counter_type' ] );
178
 
179
+ //* Admin AJAX for Gutenberg SEO Bar update.
180
+ \add_action( 'wp_ajax_the_seo_framework_update_post_data', [ $this, '_wp_ajax_get_post_data' ] );
181
+
182
  //* Admin AJAX for TSF Cropper
183
  \add_action( 'wp_ajax_tsf-crop-image', [ $this, '_wp_ajax_crop_image' ] );
 
 
 
184
  endif;
185
 
186
  /**
187
+ * @since 2.9.4
188
  * Runs after the plugin is initialized in the admin screens.
189
  * Use this to remove actions.
 
190
  */
191
  \do_action( 'the_seo_framework_after_admin_init' );
192
  }
202
  protected function init_front_end_actions() {
203
 
204
  /**
 
205
  * @since 2.8.0
206
+ * Runs before the plugin is initialized on the front-end.
207
  */
208
  \do_action( 'the_seo_framework_front_init' );
209
 
219
  //* Earlier removal of the generator tag. Doesn't require filter.
220
  \remove_action( 'wp_head', 'wp_generator' );
221
 
222
+ //* Prepares sitemap or stylesheet output.
223
+ if ( $this->can_run_sitemap() )
224
+ \add_action( 'template_redirect', [ $this, '_init_sitemap' ], 1 );
 
 
 
 
 
 
 
 
 
 
225
 
226
  //* Initialize 301 redirects.
227
  \add_action( 'template_redirect', [ $this, '_init_custom_field_redirect' ] );
228
 
229
+ //* Prepares requisite robots headers to avoid low-quality content penalties.
230
+ $this->prepare_robots_headers();
231
 
232
  //* Output meta tags.
233
  \add_action( 'wp_head', [ $this, 'html_output' ], 1 );
238
  if ( $this->get_option( 'alter_search_query' ) )
239
  $this->init_alter_search_query();
240
 
241
+ //* Alter the content feed.
242
+ \add_filter( 'the_content_feed', [ $this, 'the_content_feed' ], 10, 2 );
243
+
244
+ //* Only add the feed link to the excerpt if we're only building excerpts.
245
+ if ( $this->rss_uses_excerpt() )
246
+ \add_filter( 'the_excerpt_rss', [ $this, 'the_content_feed' ], 10, 1 );
247
+
248
  /**
249
+ * @since 2.9.4
250
  * Runs before the plugin is initialized on the front-end.
251
  * Use this to remove actions.
 
252
  */
253
  \do_action( 'the_seo_framework_after_front_init' );
254
  }
267
  * @since 2.9.3
268
  * @param bool $overwrite_titles Whether to enable title overwriting.
269
  */
270
+ if ( \apply_filters( 'the_seo_framework_overwrite_titles', true ) ) {
 
 
271
  //* Removes all pre_get_document_title filters.
272
  \remove_all_filters( 'pre_get_document_title', false );
273
 
278
 
279
  /**
280
  * @since 2.4.1
281
+ * @param bool $overwrite_titles Whether to enable legacy title overwriting.
282
  */
283
  if ( \apply_filters( 'the_seo_framework_manipulate_title', true ) ) {
284
  \remove_all_filters( 'wp_title', false );
285
  //* Override WordPress Title
286
+ \add_filter( 'wp_title', [ $this, 'get_wp_title' ], 9 );
287
  }
288
  }
289
+
290
+ if ( $this->get_option( 'og_tags' ) ) {
291
+ //* Disable Jetpack's Open Graph tags. But Sybre, compat files? Yes.
292
+ \add_filter( 'jetpack_enable_open_graph', '__return_false' );
293
+ }
294
+
295
+ if ( $this->get_option( 'twitter_tags' ) ) {
296
+ //* Disable Jetpack's Twitter Card tags. But Sybre, compat files? Maybe.
297
+ \add_filter( 'jetpack_disable_twitter_cards', '__return_true' );
298
+ // Future, maybe. See <https://github.com/Automattic/jetpack/issues/13146#issuecomment-516841698>
299
+ // \add_filter( 'jetpack_enable_twitter_cards', '__return_false' );
300
+ }
301
  }
302
 
303
  /**
304
  * Runs header actions.
305
  *
306
  * @since 3.1.0
 
307
  *
308
  * @param string $location Either 'before' or 'after'.
309
  * @return string The filter output.
322
  $functions = (array) \apply_filters( "the_seo_framework_{$location}_output", [] );
323
 
324
  foreach ( $functions as $function ) {
325
+ if ( ! empty( $function['callback'] ) ) {
326
+ $args = isset( $function['args'] ) ? $function['args'] : '';
327
+
328
+ $output .= call_user_func_array( $function['callback'], (array) $args );
 
 
329
  }
330
  }
331
 
340
  * @since 3.0.0 Now converts timezone if needed.
341
  * @since 3.1.0 1. Now no longer outputs anything on preview.
342
  * 2. Now no longer outputs anything on blocked post types.
343
+ * @since 4.0.0 Now no longer outputs anything on Customizer.
344
+ * @access private
345
  */
346
  public function html_output() {
347
 
348
+ if ( $this->is_preview() || $this->is_customize_preview() || ! $this->query_supports_seo() ) return;
349
 
350
  /**
351
  * @since 2.6.0
373
  $robots = $this->robots();
374
 
375
  /**
 
376
  * @since 2.6.0
377
+ * @param string $before The content before the SEO output. Stored in object cache.
378
  */
379
  $before = (string) \apply_filters( 'the_seo_framework_pre', '' );
380
 
437
  $after_legacy = $this->get_legacy_header_filters_output( 'after' );
438
 
439
  /**
 
440
  * @since 2.6.0
441
+ * @param string $after The content after the SEO output. Stored in object cache.
442
  */
443
  $after = (string) \apply_filters( 'the_seo_framework_pro', '' );
444
 
447
  $this->use_object_cache and $this->object_cache_set( $cache_key, $output, DAY_IN_SECONDS );
448
  endif;
449
 
450
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- $output is escaped.
451
+ echo PHP_EOL, $this->get_plugin_indicator( 'before' ), $output, $this->get_plugin_indicator( 'after', $init_start ), PHP_EOL;
 
 
 
452
 
453
  /**
454
  * @since 2.6.0
460
  * Redirects singular page to an alternate URL.
461
  *
462
  * @since 2.9.0
463
+ * @since 3.1.0: 1. Now no longer redirects on preview.
464
+ * 2. Now listens to post type settings.
465
+ * @since 4.0.0: 1. No longer tries to redirect on "search".
466
+ * 2. Added term redirect support.
467
+ * 3. No longer redirects on Customizer.
468
  * @access private
469
  *
470
  * @return void early on non-singular pages.
471
  */
472
  public function _init_custom_field_redirect() {
473
 
474
+ if ( $this->is_preview() || $this->is_customize_preview() || ! $this->query_supports_seo() ) return;
475
+
476
+ $url = '';
477
+
478
+ if ( $this->is_singular() ) {
479
+ // TODO excluse is_singular_archive()? Those can create issues...
480
+
481
+ $url = $this->get_post_meta_item( 'redirect' ) ?: '';
482
+ } elseif ( $this->is_term_meta_capable() ) {
483
+ $term_meta = $this->get_current_term_meta();
484
+
485
+ if ( isset( $term_meta['redirect'] ) )
486
+ $url = $term_meta['redirect'] ?: '';
487
+ }
488
 
 
489
  $url and $this->do_redirect( $url );
490
  }
491
 
526
  $path = $this->set_url_scheme( $url, 'relative' );
527
  $url = \trailingslashit( $this->get_home_host() ) . ltrim( $path, ' /' );
528
 
529
+ // Maintain current request's scheme.
530
  $scheme = $this->is_ssl() ? 'https' : 'http';
531
 
532
  \wp_safe_redirect( $this->set_url_scheme( $url, $scheme ), $redirect_type );
533
  exit;
534
  }
535
 
536
+ // phpcs:ignore, WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- intended feature. Disable via $this->allow_external_redirect().
537
+ \wp_redirect( $url, $redirect_type );
538
  exit;
539
  }
540
 
541
+ /**
542
+ * Prepares sitemap output.
543
+ *
544
+ * @since 4.0.0
545
+ * @access private
546
+ */
547
+ public function _init_sitemap() {
548
+ Bridges\Sitemap::get_instance()->_init();
549
+ }
550
+
551
  /**
552
  * Edits the robots.txt output.
553
  * Requires not to have a robots.txt file in the root directory.
575
 
576
  if ( $this->use_object_cache ) {
577
  $cache_key = $this->get_robots_txt_cache_key();
578
+ $output = $this->object_cache_get( $cache_key );
579
  } else {
580
  $output = false;
581
  }
583
  if ( false === $output ) :
584
  $output = '';
585
 
586
+ if ( $this->is_subdirectory_installation() ) {
 
 
 
587
  $output .= '# This is an invalid robots.txt location.' . "\r\n";
588
  $output .= '# Please visit: ' . \esc_url( \trailingslashit( $this->set_preferred_url_scheme( $this->get_home_host() ) ) . 'robots.txt' ) . "\r\n";
589
  $output .= "\r\n";
590
  }
591
 
592
+ $site_path = \esc_attr( parse_url( \site_url(), PHP_URL_PATH ) ) ?: '';
 
593
 
594
  /**
 
595
  * @since 2.5.0
596
  * @param string $pre The output before this plugin's output.
597
+ * Don't forget to add line breaks ( "\r\n" || PHP_EOL )!
598
  */
599
  $output .= (string) \apply_filters( 'the_seo_framework_robots_txt_pre', '' );
600
 
608
  * @param bool $disallow Whether to disallow robots queries.
609
  */
610
  if ( \apply_filters( 'the_seo_framework_robots_disallow_queries', false ) ) {
611
+ $output .= "Disallow: /*?*\r\n";
612
  }
613
 
614
  /**
 
615
  * @since 2.5.0
616
  * @param string $pro The output after this plugin's output.
617
+ * Don't forget to add line breaks ( "\r\n" || PHP_EOL )!
618
  */
619
  $output .= (string) \apply_filters( 'the_seo_framework_robots_txt_pro', '' );
620
 
621
  //* Add extra whitespace and sitemap full URL
622
+ if ( $this->can_do_sitemap_robots( true ) ) {
623
+ $sitemaps = Bridges\Sitemap::get_instance();
624
+ foreach ( $sitemaps->get_sitemap_endpoint_list() as $id => $data ) {
625
+ if ( ! empty( $data['robots'] ) ) {
626
+ $output .= sprintf( "\r\nSitemap: %s", \esc_url( $sitemaps->get_expected_sitemap_endpoint_url( $id ) ) );
627
+ }
628
+ }
629
+ $output .= "\r\n";
630
+ }
631
 
632
  $this->use_object_cache and $this->object_cache_set( $cache_key, $output, 86400 );
633
  endif;
634
 
635
+ // Completely override robots with output.
 
 
 
636
  $robots_txt = $output;
637
 
638
  return $robots_txt;
639
  }
640
 
641
+ /**
642
+ * Prepares the X-Robots-Tag headers for various endpoints.
643
+ *
644
+ * @since 4.0.2
645
+ */
646
+ protected function prepare_robots_headers() {
647
+
648
+ \add_action( 'template_redirect', [ $this, '_init_robots_headers' ] );
649
+ \add_action( 'the_seo_framework_sitemap_header', [ $this, '_output_robots_noindex_headers' ] );
650
+
651
+ // This is not necessarily a WordPress query. Test it inline.
652
+ if ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST )
653
+ $this->_output_robots_noindex_headers();
654
+ }
655
+
656
+ /**
657
+ * Sets the X-Robots-Tag headers on various endpoints.
658
+ *
659
+ * @since 4.0.0
660
+ * @access private
661
+ */
662
+ public function _init_robots_headers() {
663
+
664
+ if ( $this->is_feed() || $this->is_robots() ) {
665
+ $this->_output_robots_noindex_headers();
666
+ }
667
+ }
668
+
669
+ /**
670
+ * Sets the X-Robots tag headers to 'noindex'.
671
+ *
672
+ * @since 4.0.0
673
+ * @access private
674
+ */
675
+ public function _output_robots_noindex_headers() {
676
+
677
+ if ( ! headers_sent() ) {
678
+ header( 'X-Robots-Tag: noindex', true );
679
+ }
680
+ }
681
+
682
  /**
683
  * Initializes search query adjustments.
684
  *
685
  * @since 2.9.4
686
  */
687
  public function init_alter_search_query() {
688
+ switch ( $this->get_option( 'alter_search_query_type' ) ) :
 
 
 
689
  case 'post_query':
690
  \add_filter( 'the_posts', [ $this, '_alter_search_query_post' ], 10, 2 );
691
  break;
703
  * @since 2.9.4
704
  */
705
  public function init_alter_archive_query() {
706
+ switch ( $this->get_option( 'alter_archive_query_type' ) ) :
 
 
 
707
  case 'post_query':
708
  \add_filter( 'the_posts', [ $this, '_alter_archive_query_post' ], 10, 2 );
709
  break;
792
  * @since 2.9.4
793
  * @access private
794
  *
795
+ * @param array $posts The array of retrieved posts.
796
  * @param \WP_Query $wp_query The WP_Query instance.
797
  * @return array $posts
798
  */
803
  return $posts;
804
 
805
  foreach ( $posts as $n => $post ) {
806
+ if ( $this->get_post_meta_item( 'exclude_local_search', $post->ID ) ) {
807
  unset( $posts[ $n ] );
808
  }
809
  }
820
  * @since 2.9.4
821
  * @access private
822
  *
823
+ * @param array $posts The array of retrieved posts.
824
  * @param \WP_Query $wp_query The WP_Query instance.
825
  * @return array $posts
826
  */
831
  return $posts;
832
 
833
  foreach ( $posts as $n => $post ) {
834
+ if ( $this->get_post_meta_item( 'exclude_from_archive', $post->ID ) ) {
835
  unset( $posts[ $n ] );
836
  }
837
  }
848
  * @since 2.9.4
849
  * @since 3.1.0 Now checks for the post type.
850
  *
851
+ * @param \WP_Query $wp_query WP_Query object.
852
  * @return bool
853
  */
854
+ protected function is_archive_query_adjustment_blocked( $wp_query ) {
855
 
856
  static $has_filter = null;
857
 
inc/classes/inpost.class.php DELETED
@@ -1,409 +0,0 @@
1
- <?php
2
- /**
3
- * @package The_SEO_Framework\Classes
4
- */
5
- namespace The_SEO_Framework;
6
-
7
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
8
-
9
- /**
10
- * The SEO Framework plugin
11
- * Copyright (C) 2015 - 2019 Sybre Waaijer, CyberWire (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
- /**
27
- * Class The_SEO_Framework\Inpost
28
- *
29
- * Outputs Taxonomy, Post and Page meta boxes
30
- *
31
- * @since 2.8.0
32
- */
33
- class Inpost extends Profile {
34
-
35
- /**
36
- * Defines inpost nonce name.
37
- *
38
- * @since 2.7.0
39
- * @since 3.2.0 Added '_nonce' suffix.
40
- *
41
- * @var string The nonce name.
42
- */
43
- public $inpost_nonce_name = 'tsf_inpost_seo_settings_nonce';
44
-
45
- /**
46
- * Defines inpost nonce field.
47
- *
48
- * @since 2.7.0
49
- *
50
- * @var string The nonce field.
51
- */
52
- public $inpost_nonce_field = 'tsf_inpost_nonce';
53
-
54
- /**
55
- * Outputs in-post flex navigational wrapper and its content.
56
- *
57
- * @since 2.9.0
58
- * @since 3.0.0: Converted to view.
59
- *
60
- * @param string $id The Nav Tab ID
61
- * @param array $tabs the tab content {
62
- * $tabs = tab ID key = array(
63
- * $tabs['name'] => tab name
64
- * $tabs['callback'] => string|array callback function
65
- * $tabs['dashicon'] => string Dashicon
66
- * $tabs['args'] => mixed optional callback function args
67
- * )
68
- * }
69
- * @param string $version the The SEO Framework version for debugging. May be emptied.
70
- * @param bool $use_tabs Whether to output tabs, only works when $tabs count is greater than 1.
71
- */
72
- public function inpost_flex_nav_tab_wrapper( $id, $tabs = [], $version = '2.3.6', $use_tabs = true ) {
73
- $this->get_view( 'inpost/wrap-nav', get_defined_vars() );
74
- $this->get_view( 'inpost/wrap-content', get_defined_vars() );
75
- }
76
-
77
- /**
78
- * Adds the SEO meta box to post edit screens.
79
- *
80
- * @since 2.0.0
81
- * @since 3.1.0 No longer checks for SEO plugin presence.
82
- */
83
- public function add_inpost_seo_box_init() {
84
-
85
- /**
86
- * @since 2.0.0
87
- * @param bool $show_seobox Whether to show the SEO meta box.
88
- */
89
- $show_seobox = (bool) \apply_filters( 'the_seo_framework_seobox_output', true );
90
-
91
- if ( $show_seobox )
92
- \add_action( 'add_meta_boxes', [ $this, 'add_inpost_seo_box' ], 10, 1 );
93
- }
94
-
95
- /**
96
- * Adds SEO Meta boxes within Taxonomy screens.
97
- *
98
- * @since 2.1.8
99
- * @since 2.6.0 Can no longer run outside of the term edit scope.
100
- * @since 2.6.0 Can no longer run when another SEO plugin is active.
101
- * @since 2.8.0 Added show_ui argument for public taxonomy detection.
102
- * @since 3.1.0 1. No longer checks for SEO plugin presence.
103
- * 2. Now tests for the current taxonomy.
104
- * 3. Now only registers the action for the current taxonomy.
105
- */
106
- public function add_taxonomy_seo_box_init() {
107
-
108
- if ( ! $this->is_term_edit() ) // implies "show_ui"
109
- return;
110
-
111
- if ( ! $this->taxonomy_supports_custom_seo( $this->get_current_taxonomy() ) )
112
- return;
113
-
114
- /**
115
- * High priority, this box is seen right below the post/page edit screen.
116
- * @since 2.6.0
117
- * @param int $priority The metabox term priority.
118
- */
119
- $priority = (int) \apply_filters( 'the_seo_framework_term_metabox_priority', 0 );
120
-
121
- //* Add taxonomy meta box
122
- \add_action( $this->get_current_taxonomy() . '_edit_form', [ $this, '_insert_seo_meta_box' ], $priority, 2 );
123
- }
124
-
125
- /**
126
- * Adds SEO Meta boxes beneath every page/post edit screen.
127
- *
128
- * @param string $post_type The current Post Type.
129
- *
130
- * @since 2.0.0
131
- */
132
- public function add_inpost_seo_box( $post_type ) {
133
-
134
- /**
135
- * @uses $this->post_type_supports_custom_seo()
136
- * @since 2.3.9
137
- */
138
- if ( ! $this->post_type_supports_custom_seo( $post_type ) )
139
- return;
140
-
141
- $label = $this->get_post_type_label( $post_type );
142
-
143
- /**
144
- * @since 2.6.0
145
- * @NOTE warning: might cause CSS and JS conflicts.
146
- * @TODO solve note.
147
- * @param string $id The metabox class/ID.
148
- */
149
- $id = (string) \apply_filters( 'the_seo_framework_metabox_id', 'tsf-inpost-box' );
150
-
151
- /**
152
- * @since 2.9.0
153
- * @param string $context, default 'normal'. Accepts 'normal', 'side' and 'advanced'.
154
- */
155
- $context = (string) \apply_filters( 'the_seo_framework_metabox_context', 'normal' );
156
-
157
- /**
158
- * High priority, this box is seen right below the post/page edit screen.
159
- * @since 2.6.0
160
- * @param string $default Accepts 'high', 'default', 'low'
161
- */
162
- $priority = (string) \apply_filters( 'the_seo_framework_metabox_priority', 'high' );
163
-
164
- if ( $this->is_front_page_by_id( $this->get_the_real_ID() ) ) {
165
- if ( $this->can_access_settings() ) {
166
- $schema = \is_rtl() ? '%2$s - %1$s' : '%1$s - %2$s';
167
- $title = sprintf(
168
- $schema,
169
- \__( 'Homepage SEO Settings', 'autodescription' ),
170
- $this->make_info(
171
- \__( 'The SEO Settings take precedence over these settings.', 'autodescription' ),
172
- $this->seo_settings_page_url(),
173
- false
174
- )
175
- );
176
- } else {
177
- $title = \__( 'Homepage SEO Settings', 'autodescription' );
178
- }
179
- } else {
180
- /* translators: %s = Post Type */
181
- $title = sprintf( \__( '%s SEO Settings', 'autodescription' ), $label );
182
- }
183
-
184
- /* translators: %s = Post type name */
185
- \add_meta_box( $id, $title, [ $this, '_insert_seo_meta_box' ], $post_type, $context, $priority, [ $label, 'is_post_page' ] );
186
- }
187
-
188
- /**
189
- * Determines post type and returns the SEO box for either a post or term.
190
- *
191
- * @since 3.1.0 : Introduced in 2.1.8, but the name changed.
192
- * @access private
193
- *
194
- * @param mixed $object The page/post/taxonomy object.
195
- * @param array $args The page/post arguments or taxonomy slug.
196
- */
197
- public function _insert_seo_meta_box( $object, $args ) {
198
-
199
- if ( isset( $args['args'] ) ) {
200
- $args_split = $args['args'];
201
-
202
- $page = $args_split[1];
203
-
204
- // Return $args as array on post/page
205
- if ( 'is_post_page' === $page ) {
206
- // Note: Passes through object.
207
- $this->output_seo_meta_box( $object, (array) $args );
208
- }
209
- } else {
210
- $this->output_seo_meta_box( $object, '' );
211
- }
212
- }
213
-
214
- /**
215
- * Gets the SEO meta box, for either a post or term.
216
- *
217
- * @since 3.1.0 : Introduced in 2.0.0, but the name changed.
218
- *
219
- * @param mixed $object The page/post/taxonomy object.
220
- * @param array $args The page/post arguments or taxonomy slug.
221
- */
222
- protected function output_seo_meta_box( $object, $args ) {
223
-
224
- //* Determines if it's inside a meta box or within a taxonomy page.
225
- $is_term = false;
226
-
227
- // Args are passed.
228
- if ( isset( $args['args'] ) ) {
229
- //* The post type callback arg (translated)
230
- $type = $args['args'][0];
231
- //* The kind of page we're on.
232
- $page = $args['args'][1];
233
-
234
- //* Only add nonce on post/page edit screen. Nonce for terms are handled in core.
235
- if ( 'is_post_page' === $page ) {
236
- \wp_nonce_field( $this->inpost_nonce_field, $this->inpost_nonce_name );
237
- } else {
238
- // This shouldn't happen.
239
- return;
240
- }
241
- } elseif ( isset( $object->taxonomy ) ) {
242
- //* Singular name.
243
- $type = $this->get_tax_type_label( $object->taxonomy );
244
- $is_term = true;
245
- }
246
-
247
- //* Echo output.
248
- if ( $is_term ) {
249
- $this->tt_inpost_box( $type, $object );
250
- } else {
251
- $this->singular_inpost_box( $type );
252
- }
253
- }
254
-
255
- /**
256
- * Callback function for Taxonomy and Terms inpost box.
257
- *
258
- * @since 2.9.0
259
- * @since 3.1.0 Now is protected.
260
- *
261
- * @param string $type The TT type name.
262
- * @param \WP_Term $object The TT object.
263
- */
264
- protected function tt_inpost_box( $type, $object ) {
265
- /**
266
- * @since 2.9.0
267
- */
268
- \do_action( 'the_seo_framework_pre_tt_inpost_box' );
269
- $this->get_view( 'inpost/seo-settings-tt', get_defined_vars() );
270
- /**
271
- * @since 2.9.0
272
- */
273
- \do_action( 'the_seo_framework_pro_tt_inpost_box' );
274
- }
275
-
276
- /**
277
- * Returns the inpost tabs.
278
- *
279
- * @since 3.1.0
280
- *
281
- * @param $type The post type label.
282
- * @return array
283
- */
284
- protected function get_inpost_tabs( $label ) {
285
-
286
- $default_tabs = [
287
- 'general' => [
288
- 'name' => \__( 'General', 'autodescription' ),
289
- 'callback' => [ $this, 'singular_inpost_box_general_tab' ],
290
- 'dashicon' => 'admin-generic',
291
- 'args' => [ $label ],
292
- ],
293
- 'social' => [
294
- 'name' => \__( 'Social', 'autodescription' ),
295
- 'callback' => [ $this, 'singular_inpost_box_social_tab' ],
296
- 'dashicon' => 'share',
297
- 'args' => [ $label ],
298
- ],
299
- 'visibility' => [
300
- 'name' => \__( 'Visibility', 'autodescription' ),
301
- 'callback' => [ $this, 'singular_inpost_box_visibility_tab' ],
302
- 'dashicon' => 'visibility',
303
- 'args' => [ $label ],
304
- ],
305
- ];
306
-
307
- /**
308
- * Allows for altering the inpost SEO settings metabox tabs.
309
- *
310
- * @since 2.9.0
311
- *
312
- * @param array $default_tabs The default tabs.
313
- * @param string $label The current post type display name, like "Post", "Page", "Product".
314
- */
315
- $tabs = (array) \apply_filters( 'the_seo_framework_inpost_settings_tabs', $default_tabs, $label );
316
-
317
- return $tabs;
318
- }
319
-
320
- /**
321
- * Outputs the singular inpost SEO box.
322
- *
323
- * Callback function for Post and Pages inpost metabox.
324
- *
325
- * @since 2.9.0
326
- * @since 3.1.0 Now is protected.
327
- *
328
- * @param string $type The post type name.
329
- */
330
- protected function singular_inpost_box( $type ) {
331
- /**
332
- * @since 2.9.0
333
- */
334
- \do_action( 'the_seo_framework_pre_page_inpost_box' );
335
- $this->is_gutenberg_page()
336
- and $this->get_view( 'inpost/seo-settings-singular-gutenberg-data', get_defined_vars() );
337
- $this->get_view( 'inpost/seo-settings-singular', get_defined_vars() );
338
- /**
339
- * @since 2.9.0
340
- */
341
- \do_action( 'the_seo_framework_pro_page_inpost_box' );
342
- }
343
-
344
- /**
345
- * Outputs the singular inpost SEO box general tab.
346
- *
347
- * Callback function for Post and Pages inpost metabox.
348
- *
349
- * @since 2.9.0
350
- * @since 3.1.0 Now is protected.
351
- *
352
- * @param string $type The post type name.
353
- */
354
- protected function singular_inpost_box_general_tab( $type ) {
355
- /**
356
- * @since 2.9.0
357
- */
358
- \do_action( 'the_seo_framework_pre_page_inpost_general_tab' );
359
- $this->get_view( 'inpost/seo-settings-singular', get_defined_vars(), 'general' );
360
- /**
361
- * @since 2.9.0
362
- */
363
- \do_action( 'the_seo_framework_pro_page_inpost_general_tab' );
364
- }
365
-
366
- /**
367
- * Outputs the singular inpost SEO box visibility tab.
368
- *
369
- * Callback function for Post and Pages inpost metabox.
370
- *
371
- * @since 2.9.0
372
- * @since 3.1.0 Now is protected.
373
- *
374
- * @param string $type The post type name.
375
- */
376
- protected function singular_inpost_box_visibility_tab( $type ) {
377
- /**
378
- * @since 2.9.0
379
- */
380
- \do_action( 'the_seo_framework_pre_page_inpost_visibility_tab' );
381
- $this->get_view( 'inpost/seo-settings-singular', get_defined_vars(), 'visibility' );
382
- /**
383
- * @since 2.9.0
384
- */
385
- \do_action( 'the_seo_framework_pro_page_inpost_visibility_tab' );
386
- }
387
-
388
- /**
389
- * Outputs the singular inpost SEO box social tab.
390
- *
391
- * Callback function for Post and Pages inpost metabox.
392
- *
393
- * @since 2.9.0
394
- * @since 3.1.0 Now is protected.
395
- *
396
- * @param string $type The post type name.
397
- */
398
- protected function singular_inpost_box_social_tab( $type ) {
399
- /**
400
- * @since 2.9.0
401
- */
402
- \do_action( 'the_seo_framework_pre_page_inpost_social_tab' );
403
- $this->get_view( 'inpost/seo-settings-singular', get_defined_vars(), 'social' );
404
- /**
405
- * @since 2.9.0
406
- */
407
- \do_action( 'the_seo_framework_pro_page_inpost_social_tab' );
408
- }
409
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/classes/interpreters/index.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * People who work together will win, whether it be against complex football
4
+ * defenses, or the problems of modern society.
5
+ *
6
+ * - Vince Lombardi
7
+ */
inc/classes/interpreters/seobar.class.php ADDED
@@ -0,0 +1,436 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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;
8
+
9
+ /**
10
+ * The SEO Framework plugin
11
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (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 the SEO Bar into an HTML item.
30
+ *
31
+ * @since 4.0.0
32
+ * TODO @see \the_seo_framework()->get_new_seo_bar( $args ) for easy access. (name tbd)
33
+ *
34
+ * @access public
35
+ * Note that you can't instance this class. Only static methods and properties are accessible.
36
+ */
37
+ final class SeoBar {
38
+ use \The_SEO_Framework\Traits\Enclose_Stray_Private;
39
+
40
+ const STATE_UNKNOWN = 0b0001;
41
+ const STATE_BAD = 0b0010;
42
+ const STATE_OKAY = 0b0100;
43
+ const STATE_GOOD = 0b1000;
44
+
45
+ /**
46
+ * @since 4.0.0
47
+ * @var mixed $query The current SEO Bar's query items.
48
+ */
49
+ public static $query = [];
50
+
51
+ /**
52
+ * @since 4.0.0
53
+ * @var \The_SEO_Framework\Interpreters\SeoBar $instance The instance.
54
+ */
55
+ private static $instance;
56
+
57
+ /**
58
+ * @since 4.0.0
59
+ * @var array $item The current SEO Bar item list : {
60
+ *
61
+ * }
62
+ */
63
+ private static $items = [];
64
+
65
+ /**
66
+ * Constructor.
67
+ *
68
+ * @since 4.0.0
69
+ */
70
+ private function __construct() {
71
+ static::$instance = &$this;
72
+ }
73
+
74
+ /**
75
+ * Returns this instance.
76
+ *
77
+ * @since 4.0.0
78
+ *
79
+ * @return static
80
+ */
81
+ private static function get_instance() {
82
+ static::$instance instanceof static or new static;
83
+ return static::$instance;
84
+ }
85
+
86
+ /**
87
+ * @since 4.0.0
88
+ *
89
+ * @param array $query : {
90
+ * int $id : Required. The current post or term ID.
91
+ * string $taxonomy : Optional. If not set, this will interpret it as a post.
92
+ * string $post_type : Optional. If not set, this will be automatically filled.
93
+ * This parameter is ignored for taxonomies.
94
+ * }
95
+ * @return string The SEO Bar.
96
+ */
97
+ public static function generate_bar( array $query ) {
98
+
99
+ static::$query = array_merge(
100
+ [
101
+ 'id' => 0,
102
+ 'taxonomy' => '',
103
+ 'post_type' => '',
104
+ ],
105
+ $query
106
+ );
107
+
108
+ if ( ! static::$query['id'] ) return '';
109
+
110
+ if ( ! static::$query['taxonomy'] )
111
+ static::$query['post_type'] = static::$query['post_type'] ?: \get_post_type( static::$query['id'] );
112
+
113
+ $instance = static::get_instance();
114
+ $instance->store_default_bar_items();
115
+
116
+ /**
117
+ * Add or adjust SEO Bar items here.
118
+ *
119
+ * @link Example: https://gist.github.com/sybrew/59130560fcbeb98f7580dc11c54ba174
120
+ * @since 4.0.0
121
+ * @param string $interpreter The interpreter class name.
122
+ */
123
+ \do_action( 'the_seo_framework_seo_bar', static::class );
124
+
125
+ $bar = $instance->create_seo_bar( static::$items );
126
+
127
+ // There's no need to leak memory.
128
+ $instance->clear_seo_bar_items();
129
+
130
+ return $bar;
131
+ }
132
+
133
+ /**
134
+ * Passes the SEO Bar item collection by reference.
135
+ *
136
+ * @since 4.0.0
137
+ * @collector
138
+ *
139
+ * @return array SEO Bar items. Passed by reference.
140
+ */
141
+ public function &collect_seo_bar_items() {
142
+ return static::$items;
143
+ }
144
+
145
+ /**
146
+ * Registers or overwrites an SEO Bar item.
147
+ *
148
+ * @since 4.0.0
149
+ *
150
+ * @param string $key The item key.
151
+ * @param array $item : {
152
+ * string $symbol : Required. The displayed symbol that identifies your bar.
153
+ * string $title : Required. The title of the assessment.
154
+ * string $status : Required. Accepts 'good', 'okay', 'bad', 'unknown'.
155
+ * string $reason : Required. The final assessment: The reason for the $status.
156
+ * string $assess : Required. The assessments on why the reason is set. Keep it short and concise!
157
+ * Does not accept HTML for performant ARIA support.
158
+ * }
159
+ */
160
+ public static function register_seo_bar_item( $key, array $item ) {
161
+ static::$items[ $key ] = $item;
162
+ }
163
+
164
+ /**
165
+ * Passes an SEO Bar item by reference.
166
+ *
167
+ * @since 4.0.0
168
+ * @collector
169
+ * @staticvar $_void The void. If an item doesn't exist, it's put in here,
170
+ * only to be obliterated, annihilated, extirpated, eradicated, etc.
171
+ * Also, you may be able to spawn an Ender Dragon if you pass four End Crystals.
172
+ *
173
+ * @param string $key The item key.
174
+ * @return array Single SEO Bar item. Passed by reference.
175
+ */
176
+ public static function &edit_seo_bar_item( $key ) {
177
+
178
+ static $_void = [];
179
+
180
+ if ( isset( static::$items[ $key ] ) ) :
181
+ $_item = &static::$items[ $key ];
182
+ else :
183
+ $_void = [];
184
+ $_item = &$_void;
185
+ endif;
186
+
187
+ return $_item;
188
+ }
189
+
190
+ /**
191
+ * Clears the SEO Bar items.
192
+ *
193
+ * @since 4.0.0
194
+ */
195
+ private function clear_seo_bar_items() {
196
+ static::$items = [];
197
+ }
198
+
199
+ /**
200
+ * Stores the SEO Bar items.
201
+ *
202
+ * @since 4.0.0
203
+ * @factory
204
+ */
205
+ private function store_default_bar_items() {
206
+
207
+ if ( static::$query['taxonomy'] ) {
208
+ $builder = \The_SEO_Framework\Builders\SeoBar_Term::get_instance();
209
+ } else {
210
+ $builder = \The_SEO_Framework\Builders\SeoBar_Page::get_instance();
211
+ }
212
+
213
+ /**
214
+ * Adjust interpreter and builder items here.
215
+ *
216
+ * The only use we can think of here is removing items from `$builder::$tests`,
217
+ * and reading `$interpreter::$query`. Do not add tests here. Do not alter the query.
218
+ *
219
+ * @link Example: https://gist.github.com/sybrew/03dd428deadc860309879e1d5208e1c4
220
+ * @since 4.0.0
221
+ * @param string $interpreter The current class name.
222
+ * @param \The_SEO_Framework\Builders\SeoBar $builder The builder object.
223
+ */
224
+ \do_action_ref_array( 'the_seo_framework_prepare_seo_bar', [ static::class, $builder ] );
225
+
226
+ $items = &$this->collect_seo_bar_items();
227
+
228
+ foreach ( $builder->_run_test( $builder::$tests, static::$query ) as $key => $data )
229
+ $items[ $key ] = $data;
230
+ }
231
+
232
+ /**
233
+ * Converts registered items to a full HTML SEO Bar.
234
+ *
235
+ * @since 4.0.0
236
+ *
237
+ * @param array $items The SEO Bar items.
238
+ * @return string The SEO Bar
239
+ */
240
+ private function create_seo_bar( array $items ) {
241
+
242
+ $blocks = [];
243
+
244
+ foreach ( $this->generate_seo_bar_blocks( $items ) as $block )
245
+ $blocks[] = $block;
246
+
247
+ // Always return the wrap, may it be filled in via JS in the future.
248
+ return sprintf(
249
+ '<span class="tsf-seo-bar clearfix"><span class="tsf-seo-bar-inner-wrap">%s</span></span>',
250
+ implode( $blocks )
251
+ );
252
+ }
253
+
254
+ /**
255
+ * Generates SEO Bar single HTML block content.
256
+ *
257
+ * @since 4.0.0
258
+ * @generator
259
+ *
260
+ * @param array $items The SEO Bar items.
261
+ * @yield The SEO Bar HTML item.
262
+ */
263
+ private function generate_seo_bar_blocks( array $items ) {
264
+ foreach ( $items as $item )
265
+ yield vsprintf(
266
+ '<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>',
267
+ [
268
+ $this->interpret_status_to_class_suffix( $item ),
269
+ \esc_attr( $this->build_item_description( $item, 'aria' ) ),
270
+ \esc_attr( $this->build_item_description( $item, 'html' ) ),
271
+ $this->interpret_status_to_symbol( $item ),
272
+ ]
273
+ );
274
+ }
275
+
276
+ /**
277
+ * Builds the SEO Bar item description, in either HTML or plaintext.
278
+ *
279
+ * @since 4.0.0
280
+ * @staticvar array $gettext Cached gettext calls.
281
+ *
282
+ * @param array $item See `$this->register_seo_bar_item()`
283
+ * @param string $type The description type. Accepts 'html' or 'aria'.
284
+ * @return string The SEO Bar item description.
285
+ */
286
+ private function build_item_description( array $item, $type ) {
287
+
288
+ static $gettext = null;
289
+ if ( null === $gettext ) {
290
+ $gettext = [
291
+ /* translators: 1 = SEO Bar type title, 2 = Status reason. 3 = Assessments */
292
+ 'aria' => \__( '%1$s: %2$s %3$s', 'autodescription' ),
293
+ ];
294
+ }
295
+
296
+ if ( 'aria' === $type ) {
297
+ $assess = $this->enumerate_assessment_list( $item );
298
+
299
+ return sprintf(
300
+ $gettext['aria'],
301
+ $item['title'],
302
+ $item['reason'],
303
+ $this->enumerate_assessment_list( $item )
304
+ );
305
+ } else {
306
+ $assess = '<ol>';
307
+ foreach ( $item['assess'] as $_a ) {
308
+ $assess .= sprintf( '<li>%s</li>', $_a );
309
+ }
310
+ $assess .= '</ol>';
311
+
312
+ return sprintf(
313
+ '<strong>%s:</strong> %s<br>%s',
314
+ $item['title'],
315
+ $item['reason'],
316
+ $assess
317
+ );
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Enumerates the assessments in a plaintext format.
323
+ *
324
+ * @since 4.0.0
325
+ * @staticvar array $gettext Cached gettext calls.
326
+ *
327
+ * @param array $item See `$this->register_seo_bar_item()`
328
+ * @return string The SEO Bar item assessment, in plaintext.
329
+ */
330
+ private function enumerate_assessment_list( array $item ) {
331
+
332
+ $count = count( $item['assess'] );
333
+ $assessments = [];
334
+
335
+ static $gettext = null;
336
+
337
+ if ( null === $gettext ) {
338
+ $gettext = [
339
+ /* translators: 1 = Assessment number (mind the %d (D)), 2 = Assessment explanation */
340
+ 'enum' => \__( '%1$d: %2$s', 'autodescription' ),
341
+ /* translators: 1 = 'Assessment(s)', 2 = A list of assessments. */
342
+ 'list' => \__( '%1$s: %2$s', 'autodescription' ),
343
+ 'assessment' => \__( 'Assessment', 'autodescription' ),
344
+ 'assessments' => \__( 'Assessments', 'autodescription' ),
345
+ ];
346
+ }
347
+
348
+ if ( $count < 2 ) {
349
+ $assessments[] = reset( $item['assess'] );
350
+ } else {
351
+ $i = 0;
352
+ foreach ( $item['assess'] as $key => $text ) {
353
+ $assessments[] = sprintf( $gettext['enum'], ++$i, $text );
354
+ }
355
+ }
356
+
357
+ return sprintf(
358
+ $gettext['list'],
359
+ $count < 2 ? $gettext['assessment'] : $gettext['assessments'],
360
+ implode( ' ', $assessments )
361
+ );
362
+ }
363
+
364
+ /**
365
+ * Interprets binary status to a SEO Bar HTML class suffix.
366
+ *
367
+ * TODO instead of going over them in a switch, allow adding the binary data?
368
+ * This would meant hat we use the & logical operator, instead.
369
+ *
370
+ * @since 4.0.0
371
+ *
372
+ * @param array $item See `$this->register_seo_bar_item()`
373
+ * @return string The HTML class-suffix.
374
+ */
375
+ private function interpret_status_to_class_suffix( $item ) {
376
+
377
+ switch ( $item['status'] ) :
378
+ case static::STATE_GOOD:
379
+ $status = 'good';
380
+ break;
381
+
382
+ case static::STATE_OKAY:
383
+ $status = 'okay';
384
+ break;
385
+
386
+ case static::STATE_BAD:
387
+ $status = 'bad';
388
+ break;
389
+
390
+ default:
391
+ case static::STATE_UNKNOWN:
392
+ $status = 'unknown';
393
+ break;
394
+ endswitch;
395
+
396
+ return $status;
397
+ }
398
+
399
+ /**
400
+ * Enumerates the assessments in a plaintext format.
401
+ *
402
+ * @since 4.0.0
403
+ * @staticvar bool $use_symbols
404
+ *
405
+ * @param array $item See `$this->register_seo_bar_item()`
406
+ * @return string The SEO Bar item assessment, in plaintext.
407
+ */
408
+ private function interpret_status_to_symbol( array $item ) {
409
+
410
+ static $use_symbols = null;
411
+
412
+ if ( null === $use_symbols )
413
+ $use_symbols = (bool) \the_seo_framework()->get_option( 'seo_bar_symbols' );
414
+
415
+ if ( $use_symbols && $item['status'] ^ static::STATE_GOOD ) {
416
+ switch ( $item['status'] ) :
417
+ case static::STATE_OKAY:
418
+ $symbol = '!?';
419
+ break;
420
+
421
+ case static::STATE_BAD:
422
+ $symbol = '!!';
423
+ break;
424
+
425
+ default:
426
+ case static::STATE_UNKNOWN:
427
+ $symbol = '??';
428
+ break;
429
+ endswitch;
430
+
431
+ return $symbol;
432
+ }
433
+
434
+ return \esc_html( $item['symbol'] );
435
+ }
436
+ }
inc/classes/load.class.php CHANGED
@@ -1,7 +1,10 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -29,36 +32,49 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
29
  * Extending upon parent classes.
30
  *
31
  * @since 2.8.0
32
- * @uses interface Debug_Interface
33
  */
34
- final class Load extends Feed implements Debug_Interface {
 
 
 
 
 
 
35
 
36
  /**
37
- * Cached debug/profile properties. Initialized on plugins_loaded priority 5.
38
- *
39
  * @since 2.2.9
40
- *
41
- * @var bool Whether debug is enabled.
42
- * @var bool Whether transients are enabled.
43
- * @var bool Whether script debugging is enabled.
 
 
 
44
  */
45
- public $the_seo_framework_debug = false;
46
  public $the_seo_framework_use_transients = true;
47
- public $script_debug = false;
 
 
 
 
 
48
 
49
  /**
50
  * Constructor, setup debug vars and then load parent constructor.
51
  *
52
- * @staticvar int $count Prevents duplicated constructor loading.
 
53
  *
54
  * @return null If called twice or more.
55
  */
56
  public function __construct() {
57
 
58
- static $count = 0;
59
-
60
- if ( $count++ )
61
  return null;
 
62
 
63
  //= Setup debug vars before initializing anything else.
64
  $this->init_debug_vars();
@@ -72,7 +88,7 @@ final class Load extends Feed implements Debug_Interface {
72
  }
73
 
74
  //= Register the capabilities early.
75
- \add_filter( "option_page_capability_{$this->settings_field}", [ $this, 'get_settings_capability' ] );
76
 
77
  /**
78
  * @since 2.2.2
@@ -92,9 +108,6 @@ final class Load extends Feed implements Debug_Interface {
92
 
93
  //= Load plugin at init 0.
94
  \add_action( 'init', [ $this, 'init_the_seo_framework' ], 0 );
95
-
96
- //= Prepare all compatibility files early.
97
- $this->load_early_compat_files();
98
  }
99
 
100
  /**
@@ -106,7 +119,7 @@ final class Load extends Feed implements Debug_Interface {
106
 
107
  $this->the_seo_framework_debug = defined( 'THE_SEO_FRAMEWORK_DEBUG' ) && THE_SEO_FRAMEWORK_DEBUG ?: $this->the_seo_framework_debug;
108
  if ( $this->the_seo_framework_debug ) {
109
- $instance = \The_SEO_Framework\Debug::_set_instance( $this->the_seo_framework_debug );
110
  }
111
 
112
  $this->the_seo_framework_use_transients = defined( 'THE_SEO_FRAMEWORK_DISABLE_TRANSIENTS' ) && THE_SEO_FRAMEWORK_DISABLE_TRANSIENTS ? false : $this->the_seo_framework_use_transients;
@@ -116,78 +129,80 @@ final class Load extends Feed implements Debug_Interface {
116
  }
117
 
118
  /**
119
- * Wrapper for function calling through parameters. The golden nugget.
120
- *
121
- * @since 2.2.2
122
- * @since 3.1.0 Is now protected.
123
- * @NOTE _doing_it_wrong notices go towards the callback. Unless this
124
- * function is used wrongfully. Then the notice is about this function.
125
  *
126
- * @param array|string $callback the method array or function string.
127
- * @param string $version the version of The SEO Framework the function is used.
128
- * @param array|string $args The arguments passed to the function.
129
- * @return mixed $output The function called.
130
  */
131
- protected function call_function( $callback, $version = '', $args = [] ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
- $output = '';
 
 
 
 
 
 
 
134
 
135
- //? Convert string/object to array
136
- if ( is_object( $callback ) ) {
137
- $function = [ $callback, '' ];
138
- } else {
139
- $function = (array) $callback;
 
 
140
  }
141
 
142
- //? Convert string/object to array
143
- if ( is_object( $args ) ) {
144
- $args = [ $args, '' ];
145
- } else {
146
- $args = (array) $args;
 
147
  }
148
 
149
- $class = reset( $function );
150
- $method = next( $function );
151
-
152
- // Fetch method/function
153
- if ( ( is_object( $class ) || is_string( $class ) ) && $class && is_string( $method ) && $method ) {
154
- if ( get_class( $this ) === get_class( $class ) ) {
155
- if ( method_exists( $this, $method ) ) {
156
- if ( empty( $args ) ) {
157
- // In-Object calling.
158
- $output = call_user_func( [ $this, $method ] );
159
- } else {
160
- // In-Object calling.
161
- $output = call_user_func_array( [ $this, $method ], $args );
162
- }
163
- } else {
164
- $this->_inaccessible_p_or_m( \esc_html( get_class( $class ) . '->' . $method . '()' ), 'Class or Method not found.', \esc_html( $version ) );
165
- }
166
- } else {
167
- if ( method_exists( $class, $method ) ) {
168
- if ( empty( $args ) ) {
169
- $output = call_user_func( [ $class, $method ] );
170
- } else {
171
- $output = call_user_func_array( [ $class, $method ], $args );
172
- }
173
- } else {
174
- $this->_inaccessible_p_or_m( \esc_html( get_class( $class ) . '::' . $method . '()' ), 'Class or Method not found.', \esc_html( $version ) );
175
- }
176
- }
177
- } elseif ( is_string( $class ) && $class ) {
178
- //* Class is function.
179
- $func = $class;
180
-
181
- if ( empty( $args ) ) {
182
- $output = call_user_func( $func );
183
- } else {
184
- $output = call_user_func_array( $func, $args );
185
- }
186
- } else {
187
- $this->_doing_it_wrong( __METHOD__, 'Function needs to be called as a string.', \esc_html( $version ) );
188
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
 
190
- return $output;
191
  }
192
 
193
  /**
@@ -217,8 +232,8 @@ final class Load extends Feed implements Debug_Interface {
217
  * @param string $version The version of WordPress that deprecated the function.
218
  * @param string $replacement Optional. The function that should have been called. Default null.
219
  */
220
- public function _deprecated_function( $function, $version, $replacement = null ) { // phpcs:ignore -- invalid xss warning
221
- Debug::get_instance()->_deprecated_function( $function, $version, $replacement ); // phpcs:ignore -- invalid xss warning
222
  }
223
 
224
  /**
@@ -233,8 +248,8 @@ final class Load extends Feed implements Debug_Interface {
233
  * @param string $message A message explaining what has been done incorrectly.
234
  * @param string $version The version of WordPress where the message was added.
235
  */
236
- public function _doing_it_wrong( $function, $message, $version = null ) { // phpcs:ignore -- invalid xss warning
237
- Debug::get_instance()->_doing_it_wrong( $function, $message, $version ); // phpcs:ignore -- invalid xss warning
238
  }
239
 
240
  /**
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Load
4
+ *
5
+ * This is the main file called.
6
  */
7
+
8
  namespace The_SEO_Framework;
9
 
10
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
32
  * Extending upon parent classes.
33
  *
34
  * @since 2.8.0
35
+ * @since 4.0.0 No longer implements an interface. It's implied.
36
  */
37
+ final class Load extends Feed {
38
+
39
+ /**
40
+ * @since 2.4.3
41
+ * @var bool Enable object caching.
42
+ */
43
+ protected $use_object_cache = true;
44
 
45
  /**
 
 
46
  * @since 2.2.9
47
+ * @var bool $the_seo_framework_debug Whether TSF-specific debug is enabled.
48
+ */
49
+ public $the_seo_framework_debug = false;
50
+
51
+ /**
52
+ * @since 2.2.9
53
+ * @var bool $the_seo_framework_debug Whether TSF-specific transients are used.
54
  */
 
55
  public $the_seo_framework_use_transients = true;
56
+
57
+ /**
58
+ * @since 2.2.9
59
+ * @var bool $script_debug Whether WP script debugging is enabled.
60
+ */
61
+ public $script_debug = false;
62
 
63
  /**
64
  * Constructor, setup debug vars and then load parent constructor.
65
  *
66
+ * @since 2.8.0
67
+ * @since 4.0.0 Now informs developer of invalid class instancing.
68
  *
69
  * @return null If called twice or more.
70
  */
71
  public function __construct() {
72
 
73
+ if ( _has_run( __METHOD__ ) ) {
74
+ // Don't construct twice, warn developer.
75
+ $this->_doing_it_wrong( __METHOD__, 'Do not instance this class. Use function <code>the_seo_framework()</code> instead.', '3.1.0' );
76
  return null;
77
+ }
78
 
79
  //= Setup debug vars before initializing anything else.
80
  $this->init_debug_vars();
88
  }
89
 
90
  //= Register the capabilities early.
91
+ \add_filter( 'option_page_capability_' . THE_SEO_FRAMEWORK_SITE_OPTIONS, [ $this, 'get_settings_capability' ] );
92
 
93
  /**
94
  * @since 2.2.2
108
 
109
  //= Load plugin at init 0.
110
  \add_action( 'init', [ $this, 'init_the_seo_framework' ], 0 );
 
 
 
111
  }
112
 
113
  /**
119
 
120
  $this->the_seo_framework_debug = defined( 'THE_SEO_FRAMEWORK_DEBUG' ) && THE_SEO_FRAMEWORK_DEBUG ?: $this->the_seo_framework_debug;
121
  if ( $this->the_seo_framework_debug ) {
122
+ \The_SEO_Framework\Debug::_set_instance( $this->the_seo_framework_debug );
123
  }
124
 
125
  $this->the_seo_framework_use_transients = defined( 'THE_SEO_FRAMEWORK_DISABLE_TRANSIENTS' ) && THE_SEO_FRAMEWORK_DISABLE_TRANSIENTS ? false : $this->the_seo_framework_use_transients;
129
  }
130
 
131
  /**
132
+ * Requires compatibility files which are needed early or on every page.
133
+ * Mostly requires premium plugins/themes, so we check actual PHP instances,
134
+ * rather than common paths. As they can require manual FTP upload.
 
 
 
135
  *
136
+ * @since 2.8.0
137
+ * @since 4.0.0 Renamed to `_load_early_compat_files`, from `load_early_compat_files`
138
+ * @access private
 
139
  */
140
+ public function _load_early_compat_files() {
141
+
142
+ // phpcs:disable, Squiz.PHP.CommentedOutCode
143
+ // if ( ! extension_loaded( 'mbstring' ) )
144
+ // $this->_include_compat( 'mbstring', 'php' );
145
+ // phpcs:enable, Squiz.PHP.CommentedOutCode
146
+
147
+ //* Disable Headway theme SEO.
148
+ \add_filter( 'headway_seo_disabled', '__return_true' );
149
+
150
+ if ( $this->is_theme( 'genesis' ) ) {
151
+ //* Genesis Framework
152
+ $this->_include_compat( 'genesis', 'theme' );
153
+ }
154
 
155
+ if ( $this->detect_plugin( [ 'constants' => [ 'ICL_LANGUAGE_CODE' ] ] ) ) {
156
+ //* WPML
157
+ $this->_include_compat( 'wpml', 'plugin' );
158
+ }
159
+ if ( $this->detect_plugin( [ 'constants' => [ 'POLYLANG_VERSION' ] ] ) ) {
160
+ //* Polylang
161
+ $this->_include_compat( 'polylang', 'plugin' );
162
+ }
163
 
164
+ if ( $this->detect_plugin( [ 'globals' => [ 'ultimatemember' ] ] ) ) {
165
+ //* Ultimate Member
166
+ $this->_include_compat( 'ultimatemember', 'plugin' );
167
+ }
168
+ if ( $this->detect_plugin( [ 'globals' => [ 'bp' ] ] ) ) {
169
+ //* BuddyPress
170
+ $this->_include_compat( 'buddypress', 'plugin' );
171
  }
172
 
173
+ if ( $this->detect_plugin( [ 'functions' => [ 'bbpress' ] ] ) ) {
174
+ //* bbPress
175
+ $this->_include_compat( 'bbpress', 'plugin' );
176
+ } elseif ( $this->detect_plugin( [ 'constants' => [ 'WPFORO_BASENAME' ] ] ) ) {
177
+ //* wpForo
178
+ $this->_include_compat( 'wpforo', 'plugin' );
179
  }
180
 
181
+ if ( $this->detect_plugin( [ 'functions' => [ 'wc' ] ] ) ) {
182
+ //* WooCommerce.
183
+ $this->_include_compat( 'woocommerce', 'plugin' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  }
185
+ }
186
+
187
+ /**
188
+ * Includes compatibility files.
189
+ *
190
+ * @since 2.8.0
191
+ * @access private
192
+ * @staticvar array $included Maintains cache of whether files have been loaded.
193
+ *
194
+ * @param string $what The vendor/plugin/theme name for the compatibilty.
195
+ * @param string $type The compatibility type. Be it 'plugin' or 'theme'.
196
+ * @return bool True on success, false on failure. Files are expected not to return any values.
197
+ */
198
+ public function _include_compat( $what, $type = 'plugin' ) {
199
+
200
+ static $included = [];
201
+
202
+ if ( ! isset( $included[ $what ][ $type ] ) )
203
+ $included[ $what ][ $type ] = (bool) require THE_SEO_FRAMEWORK_DIR_PATH_COMPAT . $type . '-' . $what . '.php';
204
 
205
+ return $included[ $what ][ $type ];
206
  }
207
 
208
  /**
232
  * @param string $version The version of WordPress that deprecated the function.
233
  * @param string $replacement Optional. The function that should have been called. Default null.
234
  */
235
+ public function _deprecated_function( $function, $version, $replacement = null ) { // phpcs:ignore -- Wrong asserts, copied method name.
236
+ Debug::get_instance()->_deprecated_function( $function, $version, $replacement ); // phpcs:ignore -- Wrong asserts, copied method name.
237
  }
238
 
239
  /**
248
  * @param string $message A message explaining what has been done incorrectly.
249
  * @param string $version The version of WordPress where the message was added.
250
  */
251
+ public function _doing_it_wrong( $function, $message, $version = null ) { // phpcs:ignore -- Wrong asserts, copied method name.
252
+ Debug::get_instance()->_doing_it_wrong( $function, $message, $version ); // phpcs:ignore -- Wrong asserts, copied method name.
253
  }
254
 
255
  /**
inc/classes/metaboxes.class.php DELETED
@@ -1,656 +0,0 @@
1
- <?php
2
- /**
3
- * @package The_SEO_Framework\Classes
4
- */
5
- namespace The_SEO_Framework;
6
-
7
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
8
-
9
- /**
10
- * The SEO Framework plugin
11
- * Copyright (C) 2015 - 2019 Sybre Waaijer, CyberWire (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
- /**
27
- * Class The_SEO_Framework\Metaboxes
28
- *
29
- * Outputs Network and Site SEO settings meta boxes
30
- *
31
- * @since 2.8.0
32
- */
33
- class Metaboxes extends Site_Options {
34
-
35
- /**
36
- * Setting nav tab wrappers.
37
- * Outputs Tabs and settings content.
38
- *
39
- * @since 2.3.6
40
- * @since 2.6.0 Refactored.
41
- * @since 3.1.0 Now prefixes the IDs.
42
- *
43
- * @param string $id The Nav Tab ID
44
- * @param array $tabs the tab content {
45
- * $tabs = tab ID key = array(
46
- * $tabs['name'] => tab name
47
- * $tabs['callback'] => string|array callback function
48
- * $tabs['dashicon'] => string Dashicon
49
- * $tabs['args'] => mixed optional callback function args
50
- * )
51
- * }
52
- * @param string $version the The SEO Framework version for debugging. May be emptied.
53
- * @param bool $use_tabs Whether to output tabs, only works when $tabs is greater than 1.
54
- */
55
- public function nav_tab_wrapper( $id, $tabs = [], $version = '2.3.6', $use_tabs = true ) {
56
-
57
- //* Whether tabs are active.
58
- $use_tabs = $use_tabs && count( $tabs ) > 1;
59
-
60
- /**
61
- * Start navigational tabs.
62
- *
63
- * Don't output navigation if $use_tabs is false and the amount of tabs is 1 or lower.
64
- */
65
- if ( $use_tabs ) :
66
- ?>
67
- <div class="tsf-nav-tab-wrapper hide-if-no-js" id="<?php echo \esc_attr( $id . '-tabs-wrapper' ); ?>">
68
- <?php
69
- $count = 1;
70
- foreach ( $tabs as $tab => $value ) :
71
- $dashicon = isset( $value['dashicon'] ) ? $value['dashicon'] : '';
72
- $name = isset( $value['name'] ) ? $value['name'] : '';
73
-
74
- printf(
75
- '<div class=tsf-tab>%s</div>',
76
- vsprintf(
77
- '<input type=radio class="tsf-tabs-radio tsf-input-not-saved" id=%1$s name="%2$s" %3$s><label for=%1$s class=tsf-nav-tab>%4$s</label>',
78
- [
79
- \esc_attr( 'tsf-' . $id . '-tab-' . $tab ),
80
- \esc_attr( 'tsf-' . $id . '-tabs' ),
81
- ( 1 === $count ? 'checked' : '' ),
82
- sprintf(
83
- '%s%s',
84
- ( $dashicon ? '<span class="dashicons dashicons-' . \esc_attr( $dashicon ) . ' tsf-dashicons-tabs"></span>' : '' ),
85
- ( $name ? '<span class="tsf-nav-desktop">' . \esc_attr( $name ) . '</span>' : '' )
86
- ),
87
- ]
88
- )
89
- ); // xss ok: Validator can't distinguish HTML in ternary.
90
- $count++;
91
- endforeach;
92
- ?>
93
- </div>
94
- <?php
95
- endif;
96
-
97
- /**
98
- * Start Content.
99
- *
100
- * The content is relative to the navigation and outputs navigational tabs too, but uses CSS to become invisible on JS.
101
- */
102
- $count = 1;
103
- foreach ( $tabs as $tab => $value ) :
104
-
105
- $the_id = 'tsf-' . $id . '-tab-' . $tab . '-content';
106
- $the_name = 'tsf-' . $id . '-tabs-content';
107
-
108
- //* Current tab for JS.
109
- $current = 1 === $count ? ' tsf-active-tab-content' : '';
110
-
111
- ?>
112
- <div class="tsf-tabs-content <?php echo \esc_attr( $the_name . $current ); ?>" id="<?php echo \esc_attr( $the_id ); ?>" >
113
- <?php
114
- //* No-JS tabs.
115
- if ( $use_tabs ) :
116
- $dashicon = isset( $value['dashicon'] ) ? $value['dashicon'] : '';
117
- $name = isset( $value['name'] ) ? $value['name'] : '';
118
-
119
- ?>
120
- <div class="hide-if-js tsf-content-no-js">
121
- <div class="tsf-tab tsf-tab-no-js">
122
- <span class="tsf-nav-tab tsf-active-tab">
123
- <?php echo $dashicon ? '<span class="dashicons dashicons-' . \esc_attr( $dashicon ) . ' tsf-dashicons-tabs"></span>' : ''; ?>
124
- <?php echo $name ? '<span>' . \esc_attr( $name ) . '</span>' : ''; ?>
125
- </span>
126
- </div>
127
- </div>
128
- <?php
129
- endif;
130
-
131
- $callback = isset( $value['callback'] ) ? $value['callback'] : '';
132
-
133
- if ( $callback ) {
134
- $params = isset( $value['args'] ) ? $value['args'] : '';
135
- echo $this->call_function( $callback, $version, $params ); // xss ok
136
- }
137
- ?>
138
- </div>
139
- <?php
140
-
141
- $count++;
142
- endforeach;
143
- }
144
-
145
- /**
146
- * Outputs General Settings meta box on the Site SEO Settings page.
147
- *
148
- * @since 2.8.0
149
- *
150
- * @param \WP_Post|null $post The current post object.
151
- * @param array $args The metabox arguments.
152
- */
153
- public function general_metabox( $post = null, $args = [] ) {
154
- /**
155
- * @since 2.8.0
156
- */
157
- \do_action( 'the_seo_framework_general_metabox_before' );
158
- $this->get_view( 'metaboxes/general-metabox', $args );
159
- /**
160
- * @since 2.8.0
161
- */
162
- \do_action( 'the_seo_framework_general_metabox_after' );
163
- }
164
-
165
- /**
166
- * Outputs General Settings meta box general tab.
167
- *
168
- * @since 2.8.0
169
- * @since 3.1.0 Is now protected.
170
- * @see $this->general_metabox() : Callback for General Settings box.
171
- */
172
- protected function general_metabox_general_tab() {
173
- $this->get_view( 'metaboxes/general-metabox', [], 'general' );
174
- }
175
-
176
- /**
177
- * Outputs General Settings meta box layout tab.
178
- *
179
- * @since 2.8.0
180
- * @since 3.1.0 Is now protected.
181
- * @see $this->general_metabox() : Callback for General Settings box.
182
- */
183
- protected function general_metabox_layout_tab() {
184
- $this->get_view( 'metaboxes/general-metabox', [], 'layout' );
185
- }
186
-
187
- /**
188
- * Outputs General Settings meta box performance tab.
189
- *
190
- * @since 2.8.0
191
- * @since 3.1.0 Is now protected.
192
- * @see $this->general_metabox() : Callback for General Settings box.
193
- */
194
- protected function general_metabox_performance_tab() {
195
- $this->get_view( 'metaboxes/general-metabox', [], 'performance' );
196
- }
197
-
198
- /**
199
- * Outputs General Settings meta box canonical tab.
200
- *
201
- * @since 2.8.0
202
- * @since 3.1.0 Is now protected.
203
- * @see $this->general_metabox() : Callback for General Settings box.
204
- */
205
- protected function general_metabox_canonical_tab() {
206
- $this->get_view( 'metaboxes/general-metabox', [], 'canonical' );
207
- }
208
-
209
- /**
210
- * Outputs General Settings meta box timestamps tab.
211
- *
212
- * @since 3.0.0
213
- * @since 3.1.0 Is now protected.
214
- * @see $this->general_metabox() : Callback for General Settings box.
215
- */
216
- protected function general_metabox_timestamps_tab() {
217
- $this->get_view( 'metaboxes/general-metabox', [], 'timestamps' );
218
- }
219
-
220
- /**
221
- * Outputs General Settings meta box post types tab.
222
- *
223
- * @since 3.0.0
224
- * @since 3.1.0 Is now protected.
225
- * @see $this->general_metabox() : Callback for General Settings box.
226
- */
227
- protected function general_metabox_posttypes_tab() {
228
- $this->get_view( 'metaboxes/general-metabox', [], 'posttypes' );
229
- }
230
-
231
- /**
232
- * Title meta box on the Site SEO Settings page.
233
- *
234
- * @since 2.2.2
235
- *
236
- * @param \WP_Post|null $post The current post object.
237
- * @param array $args The metabox arguments.
238
- */
239
- public function title_metabox( $post = null, $args = [] ) {
240
- /**
241
- * @since 2.5.0 or earlier.
242
- */
243
- \do_action( 'the_seo_framework_title_metabox_before' );
244
- $this->get_view( 'metaboxes/title-metabox', $args );
245
- /**
246
- * @since 2.5.0 or earlier.
247
- */
248
- \do_action( 'the_seo_framework_title_metabox_after' );
249
- }
250
-
251
- /**
252
- * Title meta box general tab.
253
- *
254
- * @since 2.6.0
255
- * @since 3.1.0 Is now protected.
256
- * @see $this->title_metabox() : Callback for Title Settings box.
257
- */
258
- protected function title_metabox_general_tab() {
259
- $this->get_view( 'metaboxes/title-metabox', [], 'general' );
260
- }
261
-
262
- /**
263
- * Title meta box general tab.
264
- *
265
- * @since 2.6.0
266
- * @since 3.1.0 Is now protected.
267
- * @see $this->title_metabox() : Callback for Title Settings box.
268
- *
269
- * @param array $examples : array {
270
- * 'left' => Left Example
271
- * 'right' => Right Example
272
- * }
273
- */
274
- protected function title_metabox_additions_tab( $examples = [] ) {
275
- $this->get_view( 'metaboxes/title-metabox', get_defined_vars(), 'additions' );
276
- }
277
-
278
- /**
279
- * Title meta box prefixes tab.
280
- *
281
- * @since 2.6.0
282
- * @since 3.1.0 Is now protected.
283
- * @see $this->title_metabox() : Callback for Title Settings box.
284
- *
285
- * @param array $additions : array {
286
- * 'left' => Left Example Addtitions
287
- * 'right' => Right Example Additions
288
- * }
289
- * @param bool $showleft The example location.
290
- */
291
- protected function title_metabox_prefixes_tab( $additions = [], $showleft = false ) {
292
- $this->get_view( 'metaboxes/title-metabox', get_defined_vars(), 'prefixes' );
293
- }
294
-
295
- /**
296
- * Description meta box on the Site SEO Settings page.
297
- *
298
- * @since 2.3.4
299
- *
300
- * @param \WP_Post|null $post The current post object.
301
- * @param array $args The metabox arguments.
302
- */
303
- public function description_metabox( $post = null, $args = [] ) {
304
- /**
305
- * @since 2.5.0 or earlier.
306
- */
307
- \do_action( 'the_seo_framework_description_metabox_before' );
308
- $this->get_view( 'metaboxes/description-metabox', $args );
309
- /**
310
- * @since 2.5.0 or earlier.
311
- */
312
- \do_action( 'the_seo_framework_description_metabox_after' );
313
- }
314
-
315
- /**
316
- * Robots meta box on the Site SEO Settings page.
317
- *
318
- * @since 2.2.2
319
- *
320
- * @param \WP_Post|null $post The current post object.
321
- * @param array $args The metabox arguments.
322
- */
323
- public function robots_metabox( $post = null, $args = [] ) {
324
- /**
325
- * @since 2.5.0 or earlier.
326
- */
327
- \do_action( 'the_seo_framework_robots_metabox_before' );
328
- $this->get_view( 'metaboxes/robots-metabox', $args );
329
- /**
330
- * @since 2.5.0 or earlier.
331
- */
332
- \do_action( 'the_seo_framework_robots_metabox_after' );
333
- }
334
-
335
- /**
336
- * Robots Metabox General Tab output.
337
- *
338
- * @since 2.2.4
339
- * @see $this->robots_metabox() Callback for Robots Settings box.
340
- */
341
- protected function robots_metabox_general_tab() {
342
- $this->get_view( 'metaboxes/robots-metabox', [], 'general' );
343
- }
344
-
345
- /**
346
- * Robots Metabox "No-: Index/Follow/Archive" Tab output.
347
- *
348
- * @since 2.2.4
349
- * @see $this->robots_metabox() Callback for Robots Settings box.
350
- *
351
- * @param array $types The post types
352
- * @param array $robots The robots option values : {
353
- * 'value' string The robots option value.
354
- * 'name' string The robots name.
355
- * 'desc' string Explains what the robots type does.
356
- * }
357
- */
358
- protected function robots_metabox_no_tab( $types, $post_types, $robots ) {
359
- $this->get_view( 'metaboxes/robots-metabox', get_defined_vars(), 'no' );
360
- }
361
-
362
- /**
363
- * Outputs the Homepage meta box on the Site SEO Settings page.
364
- *
365
- * @since 2.2.2
366
- *
367
- * @param \WP_Post|null $post The current post object.
368
- * @param array $args The navigation tabs args.
369
- */
370
- public function homepage_metabox( $post = null, $args = [] ) {
371
- /**
372
- * @since 2.5.0 or earlier.
373
- */
374
- \do_action( 'the_seo_framework_homepage_metabox_before' );
375
- $this->get_view( 'metaboxes/homepage-metabox', $args );
376
- /**
377
- * @since 2.5.0 or earlier.
378
- */
379
- \do_action( 'the_seo_framework_homepage_metabox_after' );
380
- }
381
-
382
- /**
383
- * Homepage Metabox General Tab Output.
384
- *
385
- * @since 2.7.0
386
- * @since 3.1.0 Is now protected.
387
- * @see $this->homepage_metabox() Callback for Homepage Settings box.
388
- */
389
- protected function homepage_metabox_general_tab() {
390
- $this->get_view( 'metaboxes/homepage-metabox', [], 'general' );
391
- }
392
-
393
- /**
394
- * Homepage Metabox Additions Tab Output.
395
- *
396
- * @since 2.7.0
397
- * @since 3.1.0 Is now protected.
398
- * @see $this->homepage_metabox() Callback for Homepage Settings box.
399
- */
400
- protected function homepage_metabox_additions_tab() {
401
- $this->get_view( 'metaboxes/homepage-metabox', [], 'additions' );
402
- }
403
-
404
- /**
405
- * Homepage Metabox Robots Tab Output
406
- *
407
- * @since 2.7.0
408
- * @since 3.1.0 Is now protected.
409
- * @see $this->homepage_metabox() Callback for Homepage Settings box.
410
- */
411
- protected function homepage_metabox_robots_tab() {
412
- $this->get_view( 'metaboxes/homepage-metabox', [], 'robots' );
413
- }
414
-
415
- /**
416
- * Homepage Metabox Social Tab Output
417
- *
418
- * @since 2.9.0
419
- * @since 3.1.0 Is now protected.
420
- * @see $this->homepage_metabox() Callback for Homepage Settings box.
421
- */
422
- protected function homepage_metabox_social_tab() {
423
- $this->get_view( 'metaboxes/homepage-metabox', [], 'social' );
424
- }
425
-
426
- /**
427
- * Social meta box on the Site SEO Settings page.
428
- *
429
- * @since 2.2.2
430
- *
431
- * @param \WP_Post|null $post The current post object.
432
- * @param array $args the social tabs arguments.
433
- */
434
- public function social_metabox( $post = null, $args = [] ) {
435
- /**
436
- * @since 2.5.0 or earlier.
437
- */
438
- \do_action( 'the_seo_framework_social_metabox_before' );
439
- $this->get_view( 'metaboxes/social-metabox', $args );
440
- /**
441
- * @since 2.5.0 or earlier.
442
- */
443
- \do_action( 'the_seo_framework_social_metabox_after' );
444
- }
445
-
446
- /**
447
- * Social Metabox General Tab output.
448
- *
449
- * @since 2.2.2
450
- * @since 3.1.0 Is now protected.
451
- * @see $this->social_metabox() Callback for Social Settings box.
452
- */
453
- protected function social_metabox_general_tab() {
454
- $this->get_view( 'metaboxes/social-metabox', [], 'general' );
455
- }
456
-
457
- /**
458
- * Social Metabox Facebook Tab output.
459
- *
460
- * @since 2.2.2
461
- *
462
- * @see $this->social_metabox() Callback for Social Settings box.
463
- */
464
- protected function social_metabox_facebook_tab() {
465
- $this->get_view( 'metaboxes/social-metabox', [], 'facebook' );
466
- }
467
-
468
- /**
469
- * Social Metabox Twitter Tab output.
470
- *
471
- * @since 2.2.2
472
- * @see $this->social_metabox() Callback for Social Settings box.
473
- */
474
- protected function social_metabox_twitter_tab() {
475
- $this->get_view( 'metaboxes/social-metabox', [], 'twitter' );
476
- }
477
-
478
- /**
479
- * Social Metabox PostDates Tab output.
480
- *
481
- * @since 2.2.4
482
- * @see $this->social_metabox() Callback for Social Settings box.
483
- */
484
- protected function social_metabox_postdates_tab() {
485
- $this->get_view( 'metaboxes/social-metabox', [], 'postdates' );
486
- }
487
-
488
- /**
489
- * Webmaster meta box on the Site SEO Settings page.
490
- *
491
- * @since 2.2.4
492
- *
493
- * @param \WP_Post|null $post The current post object.
494
- * @param array $args the social tabs arguments.
495
- */
496
- public function webmaster_metabox( $post = null, $args = [] ) {
497
- /**
498
- * @since 2.5.0 or earlier.
499
- */
500
- \do_action( 'the_seo_framework_webmaster_metabox_before' );
501
- $this->get_view( 'metaboxes/webmaster-metabox', $args );
502
- /**
503
- * @since 2.5.0 or earlier.
504
- */
505
- \do_action( 'the_seo_framework_webmaster_metabox_after' );
506
- }
507
-
508
- /**
509
- * Sitemaps meta box on the Site SEO Settings page.
510
- *
511
- * @since 2.2.9
512
- * @see $this->sitemaps_metabox() Callback for Sitemaps Settings box.
513
- *
514
- * @param \WP_Post|null $post The current post object.
515
- * @param array $args the social tabs arguments.
516
- */
517
- public function sitemaps_metabox( $post = null, $args = [] ) {
518
- /**
519
- * @since 2.5.0 or earlier.
520
- */
521
- \do_action( 'the_seo_framework_sitemaps_metabox_before' );
522
- $this->get_view( 'metaboxes/sitemaps-metabox', $args );
523
- /**
524
- * @since 2.5.0 or earlier.
525
- */
526
- \do_action( 'the_seo_framework_sitemaps_metabox_after' );
527
- }
528
-
529
- /**
530
- * Sitemaps Metabox General Tab output.
531
- *
532
- * @since 2.2.9
533
- * @since 3.1.0 Is now protected.
534
- * @see $this->sitemaps_metabox() Callback for Sitemaps Settings box.
535
- */
536
- protected function sitemaps_metabox_general_tab() {
537
- $this->get_view( 'metaboxes/sitemaps-metabox', [], 'general' );
538
- }
539
-
540
- /**
541
- * Sitemaps Metabox Robots Tab output.
542
- *
543
- * @since 2.2.9
544
- * @since 3.1.0 Is now protected.
545
- * @see $this->sitemaps_metabox() Callback for Sitemaps Settings box.
546
- */
547
- protected function sitemaps_metabox_robots_tab() {
548
- $this->get_view( 'metaboxes/sitemaps-metabox', [], 'robots' );
549
- }
550
-
551
- /**
552
- * Sitemaps Metabox Metadata Tab output.
553
- *
554
- * @since 3.1.0
555
- * @see $this->sitemaps_metabox() Callback for Sitemaps Settings box.
556
- */
557
- protected function sitemaps_metabox_metadata_tab() {
558
- $this->get_view( 'metaboxes/sitemaps-metabox', [], 'metadata' );
559
- }
560
-
561
- /**
562
- * Sitemaps Metabox Notify Tab output.
563
- *
564
- * @since 2.2.9
565
- * @since 3.1.0 Is now protected.
566
- * @see $this->sitemaps_metabox() Callback for Sitemaps Settings box.
567
- */
568
- protected function sitemaps_metabox_notify_tab() {
569
- $this->get_view( 'metaboxes/sitemaps-metabox', [], 'notify' );
570
- }
571
-
572
- /**
573
- * Sitemaps Metabox Style Tab output.
574
- *
575
- * @since 2.8.0
576
- * @since 3.1.0 Is now protected.
577
- * @see $this->sitemaps_metabox() Callback for Sitemaps Settings box.
578
- */
579
- protected function sitemaps_metabox_style_tab() {
580
- $this->get_view( 'metaboxes/sitemaps-metabox', [], 'style' );
581
- }
582
-
583
- /**
584
- * Feed Metabox on the Site SEO Settings page.
585
- *
586
- * @since 2.5.2
587
- *
588
- * @param \WP_Post|null $post The current post object.
589
- * @param array $args the social tabs arguments.
590
- */
591
- public function feed_metabox( $post = null, $args = [] ) {
592
- /**
593
- * @since 2.5.2
594
- */
595
- \do_action( 'the_seo_framework_feed_metabox_before' );
596
- $this->get_view( 'metaboxes/feed-metabox', $args );
597
- /**
598
- * @since 2.5.2
599
- */
600
- \do_action( 'the_seo_framework_feed_metabox_after' );
601
- }
602
-
603
- /**
604
- * Schema Metabox on the Site SEO Settings page.
605
- *
606
- * @since 2.6.0
607
- *
608
- * @param \WP_Post|null $post The current post object.
609
- * @param array $args the social tabs arguments.
610
- */
611
- public function schema_metabox( $post = null, $args = [] ) {
612
- /**
613
- * @since 2.6.0
614
- */
615
- \do_action( 'the_seo_framework_schema_metabox_before' );
616
- $this->get_view( 'metaboxes/schema-metabox', $args );
617
- /**
618
- * @since 2.6.0
619
- */
620
- \do_action( 'the_seo_framework_schema_metabox_after' );
621
- }
622
-
623
- /**
624
- * Schema Metabox General Tab output.
625
- *
626
- * @since 2.8.0
627
- * @since 3.0.0 No longer used.
628
- * @since 3.1.0 Is now protected.
629
- * @see $this->schema_metabox() Callback for Schema.org Settings box.
630
- */
631
- protected function schema_metabox_general_tab() {
632
- $this->get_view( 'metaboxes/schema-metabox', [], 'general' );
633
- }
634
-
635
- /**
636
- * Schema Metabox Structure Tab output.
637
- *
638
- * @since 2.8.0
639
- * @since 3.1.0 Is now protected.
640
- * @see $this->schema_metabox() Callback for Schema.org Settings box.
641
- */
642
- protected function schema_metabox_structure_tab() {
643
- $this->get_view( 'metaboxes/schema-metabox', [], 'structure' );
644
- }
645
-
646
- /**
647
- * Schema Metabox PResence Tab output.
648
- *
649
- * @since 2.8.0
650
- * @since 3.1.0 Is now protected.
651
- * @see $this->schema_metabox() Callback for Schema.org Settings box.
652
- */
653
- protected function schema_metabox_presence_tab() {
654
- $this->get_view( 'metaboxes/schema-metabox', [], 'presence' );
655
- }
656
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/classes/post-data.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -33,359 +35,494 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
33
  class Post_Data extends Detect {
34
 
35
  /**
36
- * Return custom field post meta data.
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  *
38
- * Return only the first value of custom field. Return false if field is
39
- * blank or not set.
 
40
  *
41
- * @since 2.0.0
42
- * @staticvar array $field_cache
 
 
43
  *
44
- * @param string $field Custom field key.
45
- * @param int $post_id The post ID
46
- * @return string|boolean Return value or false on failure.
47
  */
48
- public function get_custom_field( $field, $post_id = null ) {
49
 
50
- //* If field is falsesque, get_post_meta() will return an array.
51
- if ( ! $field )
52
- return false;
 
53
 
54
- static $field_cache = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
- if ( isset( $field_cache[ $field ][ $post_id ] ) )
57
- return $field_cache[ $field ][ $post_id ];
58
 
59
- if ( empty( $post_id ) )
60
- $post_id = $this->get_the_real_ID();
 
61
 
62
- $custom_field = \get_post_meta( $post_id, $field, true );
 
63
 
64
- //* If custom field is empty, empty cache..
65
- if ( empty( $custom_field ) )
66
- $field_cache[ $field ][ $post_id ] = '';
67
 
68
- //* Render custom field, slashes stripped, sanitized if string
69
- $field_cache[ $field ][ $post_id ] = is_array( $custom_field ) ? \stripslashes_deep( $custom_field ) : stripslashes( $custom_field );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
- return $field_cache[ $field ][ $post_id ];
72
  }
73
 
74
  /**
75
- * Saves the SEO settings when we save an attachment.
76
- *
77
- * This is a passthrough method for `inpost_seo_save()`.
78
- * Sanity check is handled at `save_custom_fields()`, which `inpost_seo_save()` uses.
79
  *
80
- * @since 3.0.6
81
- * @uses $this->inpost_seo_save()
82
- * @access private
83
  *
84
- * @param integer $post_id Post ID.
85
- * @return void
86
- */
87
- public function inattachment_seo_save( $post_id ) {
88
- $this->inpost_seo_save( $post_id, \get_post( $post_id ) );
89
- }
90
-
91
- /**
92
- * Saves the SEO settings when we save a post or page.
93
- * Some values get sanitized, the rest are pulled from identically named subkeys in the $_POST['autodescription'] array.
94
  *
95
- * @since 2.0.0
96
- * @since 2.9.3 : Added 'exclude_from_archive'.
97
- * @securitycheck 3.0.0 OK. NOTE: Check is done at save_custom_fields().
98
- * @uses $this->save_custom_fields() : Perform security checks and saves post meta / custom field data to a post or page.
99
- * @access private
100
  *
101
- * @param integer $post_id Post ID.
102
- * @param \WP_Post $post Post object.
103
- * @return void
104
  */
105
- public function inpost_seo_save( $post_id, $post ) {
106
-
107
- if ( empty( $_POST['autodescription'] ) ) // CSRF ok, this is an early test to improve performance.
108
- return;
109
-
110
  /**
111
  * @since 3.1.0
112
  * @param array $defaults
113
  * @param integer $post_id Post ID.
114
  * @param \WP_Post $post Post object.
115
  */
116
- $defaults = (array) \apply_filters_ref_array( 'the_seo_framework_inpost_seo_save_defaults', [
 
117
  [
118
- '_genesis_title' => '',
119
- '_tsf_title_no_blogname' => 0, //? The prefix I should've used from the start...
120
- '_genesis_description' => '',
121
- '_genesis_canonical_uri' => '',
122
- 'redirect' => '', //! Will be displayed in custom fields when set...
123
- '_social_image_url' => '',
124
- '_social_image_id' => 0,
125
- '_genesis_noindex' => 0,
126
- '_genesis_nofollow' => 0,
127
- '_genesis_noarchive' => 0,
128
- 'exclude_local_search' => 0, //! Will be displayed in custom fields when set...
129
- 'exclude_from_archive' => 0, //! Will be displayed in custom fields when set...
130
- '_open_graph_title' => '',
131
- '_open_graph_description' => '',
132
- '_twitter_title' => '',
133
- '_twitter_description' => '',
134
- ],
135
- $post_id,
136
- $post,
137
- ] );
138
 
139
- /**
140
- * Merge user submitted options with fallback defaults
141
- * Passes through nonce at the end of the function.
142
- */
143
- // phpcs:ignore -- wp_unslash() is nonsense.
144
- $data = (array) \wp_parse_args( $_POST['autodescription'], $defaults );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
- foreach ( $data as $key => &$value ) :
147
- switch ( $key ) :
148
- case '_genesis_title':
149
- case '_open_graph_title':
150
- case '_twitter_title':
151
- $value = $this->s_title_raw( $value );
152
- continue 2;
153
-
154
- case '_genesis_description':
155
- case '_open_graph_description':
156
- case '_twitter_description':
157
- $value = $this->s_description_raw( $value );
158
- continue 2;
159
-
160
- case '_genesis_canonical_uri':
161
- case '_social_image_url':
162
- /**
163
- * Remove unwanted query parameters. They're allowed by Google, but very much rather not.
164
- * Also, they will only cause bugs.
165
- * Query parameters are also only used when no pretty permalinks are used. Which is bad.
166
- */
167
- $value = $this->s_url_query( $value );
168
- continue 2;
169
-
170
- case '_social_image_id':
171
- //* Bound to _social_image_url.
172
- $value = $data['_social_image_url'] ? $this->s_absint( $value ) : 0;
173
- continue 2;
174
 
175
- case 'redirect':
176
- //* Let's keep this as the output really is.
177
- $value = $this->s_redirect_url( $value );
178
- continue 2;
179
-
180
- case '_tsf_title_no_blogname':
181
- case '_genesis_noindex':
182
- case '_genesis_nofollow':
183
- case '_genesis_noarchive':
184
- case 'exclude_local_search':
185
- case 'exclude_from_archive':
186
- $value = $this->s_one_zero( $value );
187
- continue 2;
188
 
189
- default:
190
- // Don't process extraneous data for third party support.
191
- //* TODO set a filterable list of "allowed" option keys? -> Option Generator
192
- break;
193
- endswitch;
194
- endforeach;
195
 
196
- //* Perform nonce check and save fields.
197
- $this->save_custom_fields( $data, $this->inpost_nonce_field, $this->inpost_nonce_name, $post );
 
 
198
  }
199
 
200
  /**
201
- * Save post meta / custom field data for a post or page.
202
- *
203
- * It verifies the nonce, then checks we're not doing autosave, ajax or a future post request. It then checks the
204
- * current user's permissions, before finally* either updating the post meta, or deleting the field if the value was not
205
- * truthy.
206
  *
207
- * By passing an array of fields => values from the same metabox (and therefore same nonce) into the $data argument,
208
- * repeated checks against the nonce, request and permissions are avoided.
209
  *
210
- * @since 2.0.0
211
- * @securitycheck 3.0.0 OK.
212
- *
213
- * @thanks StudioPress (http://www.studiopress.com/) for some code.
214
- *
215
- * @param array $data Key/Value pairs of data to save in '_field_name' => 'value' format.
216
- * @param string $nonce_action Nonce action for use with wp_verify_nonce().
217
- * @param string $nonce_name Name of the nonce to check for permissions.
218
- * @param \WP_Post|integer $post Post object or ID.
219
- * @return mixed Return null if permissions incorrect, doing autosave, ajax or future post, false if update or delete
220
- * failed, and true on success.
221
  */
222
- public function save_custom_fields( array $data, $nonce_action, $nonce_name, $post ) {
223
-
224
- //* Verify the nonce
225
- // phpcs:ignore -- wp_unslash() is nonsense.
226
- if ( ! isset( $_POST[ $nonce_name ] ) || ! \wp_verify_nonce( $_POST[ $nonce_name ], $nonce_action ) )
227
- return;
228
 
229
- /**
230
- * Don't try to save the data under autosave, ajax, or future post.
231
- * @TODO find a way to maintain revisions:
232
- * @link https://github.com/sybrew/the-seo-framework/issues/48
233
- * @link https://johnblackbourn.com/post-meta-revisions-wordpress
234
- */
235
- if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
236
- return;
237
- if ( $this->doing_ajax() )
238
- return;
239
- if ( defined( 'DOING_CRON' ) && DOING_CRON )
240
- return;
241
-
242
- //* Grab the post object
243
  $post = \get_post( $post );
244
 
245
- /**
246
- * Don't save if WP is creating a revision (same as DOING_AUTOSAVE?)
247
- * @todo @see wp_is_post_revision(), which also returns the post revision ID...
248
- */
249
- if ( 'revision' === \get_post_type( $post ) )
250
- return;
251
-
252
- //* Check that the user is allowed to edit the post
253
- if ( ! \current_user_can( 'edit_post', $post->ID ) )
254
- return;
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
  /**
257
- * @since 3.1.0
258
  * @param array $data The data that's going to be saved.
259
  * @param \WP_Post $post The post object.
260
  */
261
- $data = (array) \apply_filters_ref_array( 'the_seo_framework_save_custom_fields', [
262
- $data,
263
- $post,
264
- ] );
 
 
 
265
 
266
  //* Cycle through $data, insert value or delete field
267
  foreach ( (array) $data as $field => $value ) {
268
- //* Save $value, or delete if the $value is empty
269
  if ( $value ) {
270
  \update_post_meta( $post->ID, $field, $value );
271
  } else {
 
272
  \delete_post_meta( $post->ID, $field );
273
  }
274
  }
275
  }
276
 
277
  /**
278
- * Saves primary term data for posts.
279
  *
280
- * @since 3.0.0
281
- * @securitycheck 3.0.0 OK.
 
 
 
 
 
282
  *
283
- * @param integer $post_id Post ID.
284
- * @param \WP_Post $post Post object.
285
  * @return void
286
  */
287
- public function _save_inpost_primary_term( $post_id, $post ) {
 
 
288
 
289
- //* Nonce is done at the end of this function.
290
- if ( empty( $_POST['autodescription'] ) )
291
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
- $post_type = \get_post_type( $post_id ) ?: false;
 
294
 
295
- if ( ! $post_type )
296
- return;
 
 
 
 
 
 
 
 
 
 
 
 
297
 
298
  /**
299
- * Don't save if WP is creating a revision (same as DOING_AUTOSAVE?)
300
- * @todo @see wp_is_post_revision(), which also returns the post revision ID...
 
 
 
301
  */
302
- if ( 'revision' === $post_type )
303
- return;
 
 
 
304
 
305
  //* Check that the user is allowed to edit the post
306
- if ( ! \current_user_can( 'edit_post', $post_id ) )
307
- return;
 
308
 
309
- $_taxonomies = $this->get_hierarchical_taxonomies_as( 'names', $post_type );
310
- $values = [];
311
 
312
- foreach ( $_taxonomies as $_taxonomy ) {
313
- $_post_key = '_primary_term_' . $_taxonomy;
 
314
 
315
- $values[ $_taxonomy ] = [
316
- 'action' => $this->inpost_nonce_field . '_pt',
317
- 'name' => $this->inpost_nonce_name . '_pt_' . $_taxonomy,
318
- 'value' => isset( $_POST['autodescription'][ $_post_key ] ) ? \absint( $_POST['autodescription'][ $_post_key ] ) : 0,
319
- ];
320
- }
 
 
 
 
321
 
322
- foreach ( $values as $t => $v ) {
323
- if ( \wp_verify_nonce( $v['name'], $v['action'] ) ) {
324
- $this->update_primary_term_id( $post_id, $t, $v['value'] );
325
- }
326
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  }
328
 
329
  /**
330
- * Fetches or parses the excerpt of the post.
331
- *
332
- * @since 1.0.0
333
- * @since 2.8.2 : Added 4th parameter for escaping.
334
- * @since 3.1.0 1. No longer returns anything for terms.
335
- * 2. Now strips plausible embeds URLs.
336
- *
337
- * @param string $excerpt the Excerpt.
338
- * @param int $the_id The Post ID.
339
- * @param null $deprecated No longer used.
340
- * @param bool $escape Whether to escape the excerpt.
341
- * @return string The trimmed excerpt.
342
  */
343
- public function get_excerpt_by_id( $excerpt = '', $id = '', $deprecated = null, $escape = true ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
 
345
- if ( empty( $excerpt ) )
346
- $excerpt = $this->fetch_excerpt( $id );
 
347
 
348
- //* No need to parse an empty excerpt.
349
- if ( ! $excerpt ) return '';
 
 
 
 
350
 
351
- return $escape ? $this->s_excerpt( $excerpt ) : $this->s_excerpt_raw( $excerpt );
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  }
353
 
354
  /**
355
- * Fetches excerpt from post excerpt or fetches the full post content.
356
- * Determines if a page builder is used to return an empty string.
357
- * Does not sanitize output.
358
  *
359
- * @since 2.5.2
360
- * @since 2.6.6 Detects Page builders.
361
- * @since 3.1.0 1. No longer returns anything for terms.
362
- * 2. Now strips plausible embeds URLs.
363
  *
364
- * @param \WP_Post|int|null $post The Post or Post ID. Leave null to automatically get.
365
- * @return string The excerpt.
 
366
  */
367
- public function fetch_excerpt( $post = null ) {
 
 
 
 
368
 
369
  $post = \get_post( $post );
370
 
 
 
371
  /**
372
- * Fetch custom excerpt, if not empty, from the post_excerpt field.
373
- * @since 2.5.2
 
 
 
374
  */
375
- if ( ! empty( $post->post_excerpt ) ) {
376
- $excerpt = $post->post_excerpt;
377
- } elseif ( isset( $post->post_content ) ) {
378
- $excerpt = $this->uses_page_builder( $post->ID ) ? '' : $post->post_content;
379
-
380
- if ( $excerpt ) {
381
- $excerpt = $this->strip_newline_urls( $excerpt );
382
- $excerpt = $this->strip_paragraph_urls( $excerpt );
383
- }
384
- } else {
385
- $excerpt = '';
 
 
 
 
 
 
 
 
 
 
386
  }
387
 
388
- return $excerpt;
 
 
 
 
 
389
  }
390
 
391
  /**
@@ -449,8 +586,9 @@ class Post_Data extends Detect {
449
  *
450
  * @since 2.6.6
451
  * @since 3.1.0 Added Elementor detection
 
452
  *
453
- * @param int $post_id
454
  * @return boolean
455
  */
456
  public function uses_page_builder( $post_id ) {
@@ -458,7 +596,6 @@ class Post_Data extends Detect {
458
  $meta = \get_post_meta( $post_id );
459
 
460
  /**
461
- * Determines whether a page builder has been detected.
462
  * @since 2.6.6
463
  * @since 3.1.0 1: Now defaults to `null`
464
  * 2: Now, when a boolean (either true or false) is defined, it'll short-circuit this function.
@@ -471,6 +608,9 @@ class Post_Data extends Detect {
471
  if ( is_bool( $detected ) )
472
  return $detected;
473
 
 
 
 
474
  if ( empty( $meta ) )
475
  return false;
476
 
@@ -542,7 +682,7 @@ class Post_Data extends Detect {
542
  *
543
  * @since 3.1.0
544
  *
545
- * @param int|null|\WP_Post The post ID or WP Post object.
546
  * @return bool True if draft, false otherwise.
547
  */
548
  public function is_draft( $post = null ) {
@@ -604,10 +744,9 @@ class Post_Data extends Detect {
604
 
605
  $primary_id = $this->get_primary_term_id( $post_id, $taxonomy );
606
 
607
- if ( ! $primary_id )
608
- return false;
609
 
610
- $terms = \get_the_terms( $post_id, $taxonomy );
611
  $primary_term = false;
612
 
613
  foreach ( $terms as $term ) {
@@ -630,7 +769,7 @@ class Post_Data extends Detect {
630
  * @return int The primary term ID. 0 if not set.
631
  */
632
  public function get_primary_term_id( $post_id = null, $taxonomy = '' ) {
633
- return (int) $this->get_custom_field( '_primary_term_' . $taxonomy, $post_id ) ?: 0;
634
  }
635
 
636
  /**
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Post_Data
4
+ * @subpackage The_SEO_Framework\Data
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
35
  class Post_Data extends Detect {
36
 
37
  /**
38
+ * @since 2.7.0
39
+ * @since 3.2.0 Added '_nonce' suffix.
40
+ * @var string The inpost nonce name.
41
+ */
42
+ public $inpost_nonce_name = 'tsf_inpost_seo_settings_nonce';
43
+
44
+ /**
45
+ * @since 2.7.0
46
+ * @var string The inpost nonce field.
47
+ */
48
+ public $inpost_nonce_field = 'tsf_inpost_nonce';
49
+
50
+ /**
51
+ * Returns a post SEO meta item by key.
52
  *
53
+ * Unlike other post meta calls, no \WP_Post object is accepted as an input value,
54
+ * this is done for performance reasons, so we can cache here, instead of relying on
55
+ * WordPress' cache, where they cast many filters and redundantly sanitize the object.
56
  *
57
+ * When we'll be moving to PHP 7 and later, we'll enforce type hinting.
58
+ *
59
+ * @since 4.0.0
60
+ * @since 4.0.1 Now obtains the real ID when none is supplied.
61
  *
62
+ * @param string $item The item to get.
63
+ * @param int $post_id The post ID.
64
+ * @param bool $use_cache Whether to use caching.
65
  */
66
+ public function get_post_meta_item( $item, $post_id = 0, $use_cache = true ) {
67
 
68
+ $meta = $this->get_post_meta( $post_id ?: $this->get_the_real_ID(), $use_cache );
69
+
70
+ return isset( $meta[ $item ] ) ? $meta[ $item ] : null;
71
+ }
72
 
73
+ /**
74
+ * Returns all registered custom SEO fields for a post.
75
+ *
76
+ * Unlike other post meta calls, no \WP_Post object is accepted as an input value,
77
+ * this is done for performance reasons, so we can cache here, instead of relying on
78
+ * WordPress' cache, where they cast many filters and redundantly sanitize the object.
79
+ *
80
+ * When we'll be moving to PHP 7 and later, we'll enforce type hinting.
81
+ *
82
+ * @since 4.0.0
83
+ * @since 4.0.2 Now tests for valid post ID in the post object.
84
+ * @staticvar array $cache
85
+ *
86
+ * @param int $post_id The post ID.
87
+ * @param bool $use_cache Whether to use caching.
88
+ * @return array The post meta.
89
+ */
90
+ public function get_post_meta( $post_id, $use_cache = true ) {
91
 
92
+ if ( $use_cache ) {
93
+ static $cache = [];
94
 
95
+ if ( isset( $cache[ $post_id ] ) )
96
+ return $cache[ $post_id ];
97
+ }
98
 
99
+ // get_post_meta() requires a valid post ID. Make sure that post exists.
100
+ $post = \get_post( $post_id );
101
 
102
+ if ( empty( $post->ID ) )
103
+ return $cache[ $post_id ] = [];
 
104
 
105
+ /**
106
+ * We can't trust the filter to always contain the expected keys.
107
+ * However, it may contain more keys than we anticipated. Merge them.
108
+ */
109
+ $defaults = array_merge(
110
+ $this->get_unfiltered_post_meta_defaults(),
111
+ $this->get_post_meta_defaults( $post->ID )
112
+ );
113
+
114
+ // Filter the post meta items based on defaults' keys.
115
+ $meta = array_intersect_key(
116
+ \get_post_meta( $post->ID ), // Gets all post meta. This is a discrepancy with get_term_meta()!
117
+ $defaults
118
+ );
119
+
120
+ // WP converts all entries to arrays, because we got ALL entries. Disarray!
121
+ foreach ( $meta as $key => $value ) {
122
+ $meta[ $key ] = $value[0];
123
+ }
124
 
125
+ return $cache[ $post_id ] = array_merge( $defaults, $meta );
126
  }
127
 
128
  /**
129
+ * Returns the post meta defaults.
 
 
 
130
  *
131
+ * Unlike other post meta calls, no \WP_Post object is accepted as an input value,
132
+ * this is done for performance reasons, so we can cache here, instead of relying on
133
+ * WordPress' cache, where they cast many filters and redundantly sanitize the object.
134
  *
135
+ * When we'll be moving to PHP 7 and later, we'll enforce type hinting.
 
 
 
 
 
 
 
 
 
136
  *
137
+ * @since 4.0.0
 
 
 
 
138
  *
139
+ * @param int $post_id The post ID.
140
+ * @return array The default post meta.
 
141
  */
142
+ public function get_post_meta_defaults( $post_id = 0 ) {
 
 
 
 
143
  /**
144
  * @since 3.1.0
145
  * @param array $defaults
146
  * @param integer $post_id Post ID.
147
  * @param \WP_Post $post Post object.
148
  */
149
+ return (array) \apply_filters_ref_array(
150
+ 'the_seo_framework_inpost_seo_save_defaults', // TODO rename to the_seo_framework_post_meta_defaults
151
  [
152
+ $this->get_unfiltered_post_meta_defaults(),
153
+ $post_id,
154
+ \get_post( $post_id ),
155
+ ]
156
+ );
157
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
+ /**
160
+ * Returns the unfiltered post meta defaults.
161
+ *
162
+ * @since 4.0.0
163
+ *
164
+ * @return array The default, unfiltered, post meta.
165
+ */
166
+ protected function get_unfiltered_post_meta_defaults() {
167
+ return [
168
+ '_genesis_title' => '',
169
+ '_tsf_title_no_blogname' => 0, //? The prefix I should've used from the start...
170
+ '_genesis_description' => '',
171
+ '_genesis_canonical_uri' => '',
172
+ 'redirect' => '', //! Will be displayed in custom fields when set...
173
+ '_social_image_url' => '',
174
+ '_social_image_id' => 0,
175
+ '_genesis_noindex' => 0,
176
+ '_genesis_nofollow' => 0,
177
+ '_genesis_noarchive' => 0,
178
+ 'exclude_local_search' => 0, //! Will be displayed in custom fields when set...
179
+ 'exclude_from_archive' => 0, //! Will be displayed in custom fields when set...
180
+ '_open_graph_title' => '',
181
+ '_open_graph_description' => '',
182
+ '_twitter_title' => '',
183
+ '_twitter_description' => '',
184
+ ];
185
+ }
186
 
187
+ /**
188
+ * Updates single post meta value.
189
+ *
190
+ * Note that this method can be more resource intensive than you intend it to be,
191
+ * as it reprocesses all post meta.
192
+ *
193
+ * @since 4.0.0
194
+ * @uses $this->save_post_meta() to process all data.
195
+ *
196
+ * @param string $item The item to update.
197
+ * @param mixed $value The value the item should be at.
198
+ * @param \WP_Post|integer $post The post object or post ID.
199
+ */
200
+ public function update_single_post_meta_item( $item, $value, $post ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
+ $post = \get_post( $post );
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
+ if ( ! $post ) return;
 
 
 
 
 
205
 
206
+ $meta = $this->get_post_meta( $post->ID, false );
207
+ $meta[ $item ] = $value;
208
+
209
+ $this->save_post_meta( $post->ID, $meta );
210
  }
211
 
212
  /**
213
+ * Save post meta / custom field data for a singular post type.
 
 
 
 
214
  *
215
+ * @since 4.0.0
 
216
  *
217
+ * @param \WP_Post|integer $post The post object or post ID.
218
+ * @param array $data The post meta fields, will be merged with the defaults.
 
 
 
 
 
 
 
 
 
219
  */
220
+ public function save_post_meta( $post, array $data ) {
 
 
 
 
 
221
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  $post = \get_post( $post );
223
 
224
+ if ( ! $post ) return;
225
+
226
+ $data = (array) \wp_parse_args( $data, $this->get_post_meta_defaults( $post->ID ) );
227
+ $data = $this->s_post_meta( $data );
228
+
229
+ if ( \has_filter( 'the_seo_framework_save_custom_fields' ) ) {
230
+ $this->_deprecated_filter( 'the_seo_framework_save_custom_fields', '4.0.0', 'the_seo_framework_save_post_meta' );
231
+ /**
232
+ * @since 3.1.0
233
+ * @since 4.0.0 Deprecated.
234
+ * @deprecated
235
+ * @param array $data The data that's going to be saved.
236
+ * @param \WP_Post $post The post object.
237
+ */
238
+ $data = (array) \apply_filters_ref_array(
239
+ 'the_seo_framework_save_custom_fields',
240
+ [
241
+ $data,
242
+ $post,
243
+ ]
244
+ );
245
+ }
246
 
247
  /**
248
+ * @since 4.0.0
249
  * @param array $data The data that's going to be saved.
250
  * @param \WP_Post $post The post object.
251
  */
252
+ $data = (array) \apply_filters_ref_array(
253
+ 'the_seo_framework_save_post_meta',
254
+ [
255
+ $data,
256
+ $post,
257
+ ]
258
+ );
259
 
260
  //* Cycle through $data, insert value or delete field
261
  foreach ( (array) $data as $field => $value ) {
262
+ //* Save $value, or delete if the $value is empty.
263
  if ( $value ) {
264
  \update_post_meta( $post->ID, $field, $value );
265
  } else {
266
+ // This is fine for as long as we merge the getter values with the defaults.
267
  \delete_post_meta( $post->ID, $field );
268
  }
269
  }
270
  }
271
 
272
  /**
273
+ * Saves the SEO settings when we save an attachment.
274
  *
275
+ * This is a passthrough method for `_update_post_meta()`.
276
+ * Sanity checks are handled deeper.
277
+ *
278
+ * @since 3.0.6
279
+ * @since 4.0.0 Renamed from `inattachment_seo_save`
280
+ * @uses $this->_update_post_meta()
281
+ * @access private
282
  *
283
+ * @param int $post_id The post ID.
 
284
  * @return void
285
  */
286
+ public function _update_attachment_meta( $post_id ) {
287
+ $this->_update_post_meta( $post_id, \get_post( $post_id ) );
288
+ }
289
 
290
+ /**
291
+ * Saves the Post SEO Meta settings on quick-edit, bulk-edit, or post-edit.
292
+ *
293
+ * @since 2.0.0
294
+ * @since 2.9.3 Added 'exclude_from_archive'.
295
+ * @since 4.0.0 1. Renamed from `inpost_seo_save`
296
+ * 2. Now allows updating during `WP_CRON`.
297
+ * 3. Now allows updating during `WP_AJAX`.
298
+ * @access private
299
+ *
300
+ * @param int $post_id The post ID. Unused, but sent through filter.
301
+ * @param \WP_Post $post The post object.
302
+ */
303
+ public function _update_post_meta( $post_id, $post ) {
304
+ // phpcs:disable, WordPress.Security.NonceVerification
305
+
306
+ if ( ! empty( $_POST['autodescription-quick'] ) ) {
307
+ $this->update_quick_edit_post_meta( $post_id, $post );
308
+ } elseif ( ! empty( $_REQUEST['autodescription-bulk'] ) ) {
309
+ // This is sent via GET. Keep using $_REQUEST for future-compatibility.
310
+ $this->update_bulk_edit_post_meta( $post_id, $post );
311
+ } elseif ( ! empty( $_POST['autodescription'] ) ) {
312
+ $this->update_post_edit_post_meta( $post_id, $post );
313
+ }
314
 
315
+ // phpcs:enable, WordPress.Security.NonceVerification
316
+ }
317
 
318
+ /**
319
+ * Overwrites all of the post meta on post-edit.
320
+ *
321
+ * @since 4.0.0
322
+ *
323
+ * @param int $post_id The post ID. Unused.
324
+ * @param \WP_Post $post The post object.
325
+ * @return void
326
+ */
327
+ protected function update_post_edit_post_meta( $post_id, $post ) {
328
+
329
+ $post = \get_post( $post );
330
+
331
+ if ( ! $post ) return;
332
 
333
  /**
334
+ * Don't try to save the data prior autosave, or revision post (is_preview).
335
+ *
336
+ * @TODO find a way to maintain revisions:
337
+ * @link https://github.com/sybrew/the-seo-framework/issues/48
338
+ * @link https://johnblackbourn.com/post-meta-revisions-wordpress
339
  */
340
+ if ( \wp_is_post_autosave( $post ) ) return;
341
+ if ( \wp_is_post_revision( $post ) ) return;
342
+
343
+ $nonce_name = $this->inpost_nonce_name;
344
+ $nonce_action = $this->inpost_nonce_field;
345
 
346
  //* Check that the user is allowed to edit the post
347
+ if ( ! \current_user_can( 'edit_post', $post->ID ) ) return;
348
+ if ( ! isset( $_POST[ $nonce_name ] ) ) return;
349
+ if ( ! \wp_verify_nonce( \stripslashes_from_strings_only( $_POST[ $nonce_name ] ), $nonce_action ) ) return;
350
 
351
+ $data = (array) $_POST['autodescription'];
 
352
 
353
+ //* Perform nonce check and save fields.
354
+ $this->save_post_meta( $post, $data );
355
+ }
356
 
357
+ /**
358
+ * Overwrites a part of the post meta on quick-edit.
359
+ *
360
+ * @since 4.0.0
361
+ *
362
+ * @param int $post_id The post ID. Unused.
363
+ * @param \WP_Post $post The post object.
364
+ * @return void
365
+ */
366
+ protected function update_quick_edit_post_meta( $post_id, $post ) {
367
 
368
+ $post = \get_post( $post );
369
+
370
+ if ( empty( $post->ID ) ) return;
371
+
372
+ //* Check again against ambiguous injection...
373
+ // Note, however: function wp_ajax_inline_save() already performs all these checks for us before firing this callback's action.
374
+ if ( ! \current_user_can( 'edit_post', $post->ID ) ) return;
375
+ if ( ! \check_ajax_referer( 'inlineeditnonce', '_inline_edit', false ) ) return;
376
+
377
+ $new_data = [];
378
+
379
+ foreach ( (array) $_POST['autodescription-quick'] as $key => $value ) :
380
+ switch ( $key ) :
381
+ case 'noindex':
382
+ case 'nofollow':
383
+ case 'noarchive':
384
+ $new_data[ "_genesis_$key" ] = $value;
385
+ break;
386
+
387
+ case 'redirect':
388
+ $new_data[ $key ] = $value;
389
+ break;
390
+
391
+ case 'canonical':
392
+ $new_data['_genesis_canonical_uri'] = $value;
393
+ break;
394
+
395
+ default:
396
+ break;
397
+ endswitch;
398
+ endforeach;
399
+
400
+ // Unlike the post-edit saving, we don't reset the data, just overwrite what's given.
401
+ // This is because we only update a portion of the meta.
402
+ $data = array_merge(
403
+ $this->get_post_meta( $post->ID, false ),
404
+ $new_data
405
+ );
406
+
407
+ $this->save_post_meta( $post, $data );
408
  }
409
 
410
  /**
411
+ * Overwrites a park of the post meta on bulk-edit.
412
+ *
413
+ * @since 4.0.0
414
+ * @staticvar bool $verified_referer Will hold true after the first update passes.
415
+ *
416
+ * @param int $post_id The post ID. Unused.
417
+ * @param \WP_Post $post The post object.
418
+ * @return void
 
 
 
 
419
  */
420
+ protected function update_bulk_edit_post_meta( $post_id, $post ) {
421
+
422
+ $post = \get_post( $post );
423
+
424
+ if ( empty( $post->ID ) ) return;
425
+
426
+ //* Check again against ambiguous injection...
427
+ // Note, however: function bulk_edit_posts() already performs all these checks for us before firing this callback's action.
428
+ if ( ! \current_user_can( 'edit_post', $post->ID ) ) return;
429
+
430
+ static $verified_referer = false;
431
+ if ( ! $verified_referer ) {
432
+ \check_admin_referer( 'bulk-posts' );
433
+ $verified_referer = true;
434
+ }
435
+
436
+ static $new_data = null;
437
+
438
+ if ( ! isset( $new_data ) ) {
439
+ $new_data = [];
440
 
441
+ // This is sent via GET. Keep using $_REQUEST for future-compatibility.
442
+ foreach ( (array) $_REQUEST['autodescription-bulk'] as $key => $value ) :
443
+ if ( 'nochange' === $value ) continue;
444
 
445
+ switch ( $key ) :
446
+ case 'noindex':
447
+ case 'nofollow':
448
+ case 'noarchive':
449
+ $new_data[ "_genesis_$key" ] = $value;
450
+ break;
451
 
452
+ default:
453
+ break;
454
+ endswitch;
455
+ endforeach;
456
+ }
457
+
458
+ // Unlike the post-edit saving, we don't reset the data, just overwrite what's given.
459
+ // This is because we only update a portion of the meta.
460
+ $data = array_merge(
461
+ $this->get_post_meta( $post->ID, false ),
462
+ $new_data
463
+ );
464
+
465
+ $this->save_post_meta( $post, $data );
466
  }
467
 
468
  /**
469
+ * Saves primary term data for posts.
 
 
470
  *
471
+ * @since 3.0.0
472
+ * @since 4.0.0 1. Now allows updating during `WP_CRON`.
473
+ * 2. Now allows updating during `WP_AJAX`.
474
+ * @securitycheck 3.0.0 OK.
475
  *
476
+ * @param int $post_id The post ID. Unused, but sent through filter.
477
+ * @param \WP_Post $post The post object.
478
+ * @return void
479
  */
480
+ public function _save_inpost_primary_term( $post_id, $post ) {
481
+
482
+ // The 'autodescription' index should only be used when using the editor.
483
+ // Quick and bulk-edit should be halted here.
484
+ if ( empty( $_POST['autodescription'] ) ) return;
485
 
486
  $post = \get_post( $post );
487
 
488
+ if ( empty( $post->ID ) ) return;
489
+
490
  /**
491
+ * Don't try to save the data prior autosave, or revision post (is_preview).
492
+ *
493
+ * @TODO find a way to maintain revisions:
494
+ * @link https://github.com/sybrew/the-seo-framework/issues/48
495
+ * @link https://johnblackbourn.com/post-meta-revisions-wordpress
496
  */
497
+ if ( \wp_is_post_autosave( $post ) ) return;
498
+ if ( \wp_is_post_revision( $post ) ) return;
499
+
500
+ //* Check that the user is allowed to edit the post. Nonce checks are done in bulk later.
501
+ if ( ! \current_user_can( 'edit_post', $post->ID ) ) return;
502
+
503
+ $post_type = \get_post_type( $post->ID ) ?: false;
504
+ // Can this even fail?
505
+ if ( ! $post_type ) return;
506
+
507
+ $_taxonomies = $this->get_hierarchical_taxonomies_as( 'names', $post_type );
508
+ $values = [];
509
+
510
+ foreach ( $_taxonomies as $_taxonomy ) {
511
+ $_post_key = '_primary_term_' . $_taxonomy;
512
+
513
+ $values[ $_taxonomy ] = [
514
+ 'action' => $this->inpost_nonce_field . '_pt',
515
+ 'name' => $this->inpost_nonce_name . '_pt_' . $_taxonomy,
516
+ 'value' => isset( $_POST['autodescription'][ $_post_key ] ) ? \absint( $_POST['autodescription'][ $_post_key ] ) : 0,
517
+ ];
518
  }
519
 
520
+ foreach ( $values as $t => $v ) {
521
+ if ( ! isset( $_POST[ $v['name'] ] ) ) continue;
522
+ if ( \wp_verify_nonce( \stripslashes_from_strings_only( $_POST[ $v['name'] ] ), $v['action'] ) ) {
523
+ $this->update_primary_term_id( $post->ID, $t, $v['value'] );
524
+ }
525
+ }
526
  }
527
 
528
  /**
586
  *
587
  * @since 2.6.6
588
  * @since 3.1.0 Added Elementor detection
589
+ * @since 4.0.0 Now detects page builders before looping over the meta.
590
  *
591
+ * @param int $post_id The post ID to check.
592
  * @return boolean
593
  */
594
  public function uses_page_builder( $post_id ) {
596
  $meta = \get_post_meta( $post_id );
597
 
598
  /**
 
599
  * @since 2.6.6
600
  * @since 3.1.0 1: Now defaults to `null`
601
  * 2: Now, when a boolean (either true or false) is defined, it'll short-circuit this function.
608
  if ( is_bool( $detected ) )
609
  return $detected;
610
 
611
+ if ( ! $this->detect_page_builder() )
612
+ return false;
613
+
614
  if ( empty( $meta ) )
615
  return false;
616
 
682
  *
683
  * @since 3.1.0
684
  *
685
+ * @param int|null|\WP_Post $post The post ID or WP Post object.
686
  * @return bool True if draft, false otherwise.
687
  */
688
  public function is_draft( $post = null ) {
744
 
745
  $primary_id = $this->get_primary_term_id( $post_id, $taxonomy );
746
 
747
+ if ( ! $primary_id ) return false;
 
748
 
749
+ $terms = \get_the_terms( $post_id, $taxonomy );
750
  $primary_term = false;
751
 
752
  foreach ( $terms as $term ) {
769
  * @return int The primary term ID. 0 if not set.
770
  */
771
  public function get_primary_term_id( $post_id = null, $taxonomy = '' ) {
772
+ return (int) \get_post_meta( $post_id, '_primary_term_' . $taxonomy, true ) ?: 0;
773
  }
774
 
775
  /**
inc/classes/profile.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -30,13 +32,7 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
30
  *
31
  * @since 3.0.0
32
  */
33
- class Profile extends Doing_It_Right {
34
-
35
- /**
36
- * @since 3.0.0
37
- * @var array|object The profile setting fields.
38
- */
39
- public $profile_settings = [];
40
 
41
  /**
42
  * Outputs profile fields and prepares saving thereof.
@@ -45,26 +41,34 @@ class Profile extends Doing_It_Right {
45
  */
46
  protected function init_profile_fields() {
47
 
48
- //= No need to load anything if the user can't even publish posts.
49
- if ( ! \current_user_can( 'publish_posts' ) )
50
- return;
51
 
52
- $this->profile_settings = (object) [
53
- 'keys' => [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  'facebook_page' => 'tsf_facebook_page',
55
  'twitter_page' => 'tsf_twitter_page',
56
  ],
57
- 'sanitation' => [
58
  'facebook_page' => 's_facebook_profile',
59
  'twitter_page' => 's_twitter_name',
60
  ],
61
  ];
62
-
63
- \add_action( 'show_user_profile', [ $this, '_add_user_author_fields' ], 0, 1 );
64
- \add_action( 'edit_user_profile', [ $this, '_add_user_author_fields' ], 0, 1 );
65
-
66
- \add_action( 'personal_options_update', [ $this, '_update_user_settings' ], 10, 1 );
67
- \add_action( 'edit_user_profile_update', [ $this, '_update_user_settings' ], 10, 1 );
68
  }
69
 
70
  /**
@@ -73,22 +77,23 @@ class Profile extends Doing_It_Right {
73
  * @since 3.0.0
74
  * @access private
75
  *
76
- * @param WP_User $user WP_User object.
77
  */
78
  public function _add_user_author_fields( \WP_User $user ) {
79
 
80
- if ( ! $user->has_cap( 'publish_posts' ) )
81
- return;
 
82
 
83
  $fields = [
84
- $this->profile_settings->keys['facebook_page'] => (object) [
85
  'name' => \__( 'Facebook profile page', 'autodescription' ),
86
  'type' => 'url',
87
  'placeholder' => \_x( 'https://www.facebook.com/YourPersonalProfile', 'Example Facebook Personal URL', 'autodescription' ),
88
  'value' => $this->get_user_option( $user->ID, 'facebook_page' ),
89
  'class' => '',
90
  ],
91
- $this->profile_settings->keys['twitter_page'] => (object) [
92
  'name' => \__( 'Twitter profile name', 'autodescription' ),
93
  'type' => 'text',
94
  'placeholder' => \_x( '@your-personal-username', 'Twitter @username', 'autodescription' ),
@@ -112,24 +117,24 @@ class Profile extends Doing_It_Right {
112
  */
113
  public function _update_user_settings( $user_id ) {
114
 
 
 
115
  \check_admin_referer( 'update-user_' . $user_id );
116
  if ( ! \current_user_can( 'edit_user', $user_id ) ) return;
117
 
118
- if ( empty( $_POST ) ) // input var OK.
119
- return;
120
-
121
  $user = new \WP_User( $user_id );
122
 
123
- if ( ! $user->has_cap( 'publish_posts' ) )
124
- return;
125
 
126
  $success = [];
127
  $defaults = $this->get_default_user_data();
128
 
129
- foreach ( $this->profile_settings->keys as $option => $post_key ) {
130
- if ( isset( $_POST[ $post_key ] ) ) { // Input var ok: profile_settings->keys are static.
131
- $value = $this->{$this->profile_settings->sanitation[ $option ]}( $_POST[ $post_key ] ) // Input var & sanitization OK.
132
- ?: $defaults[ $option ]; // precision alignment ok.
 
 
133
 
134
  $success[] = (bool) $this->update_user_option( $user_id, $option, $value );
135
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Profile
4
+ * @subpackage The_SEO_Framework\Admin\Profile
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
32
  *
33
  * @since 3.0.0
34
  */
35
+ class Profile extends Generate_Ldjson {
 
 
 
 
 
 
36
 
37
  /**
38
  * Outputs profile fields and prepares saving thereof.
41
  */
42
  protected function init_profile_fields() {
43
 
44
+ //= No need to load anything if the current user can't even publish posts.
45
+ if ( ! \current_user_can( 'publish_posts' ) ) return;
 
46
 
47
+ \add_action( 'show_user_profile', [ $this, '_add_user_author_fields' ], 0, 1 );
48
+ \add_action( 'edit_user_profile', [ $this, '_add_user_author_fields' ], 0, 1 );
49
+
50
+ \add_action( 'personal_options_update', [ $this, '_update_user_settings' ], 10, 1 );
51
+ \add_action( 'edit_user_profile_update', [ $this, '_update_user_settings' ], 10, 1 );
52
+ }
53
+
54
+ /**
55
+ * Returns the current profile field settings.
56
+ *
57
+ * @since 4.0.0
58
+ *
59
+ * @return \stdClass The profile settings.
60
+ */
61
+ protected function get_profile_field_settings() {
62
+ return (object) [
63
+ 'keys' => [
64
  'facebook_page' => 'tsf_facebook_page',
65
  'twitter_page' => 'tsf_twitter_page',
66
  ],
67
+ 'sanitization' => [
68
  'facebook_page' => 's_facebook_profile',
69
  'twitter_page' => 's_twitter_name',
70
  ],
71
  ];
 
 
 
 
 
 
72
  }
73
 
74
  /**
77
  * @since 3.0.0
78
  * @access private
79
  *
80
+ * @param \WP_User $user WP_User object.
81
  */
82
  public function _add_user_author_fields( \WP_User $user ) {
83
 
84
+ if ( ! $user->has_cap( 'publish_posts' ) ) return;
85
+
86
+ $_field_settings = $this->get_profile_field_settings();
87
 
88
  $fields = [
89
+ $_field_settings->keys['facebook_page'] => (object) [
90
  'name' => \__( 'Facebook profile page', 'autodescription' ),
91
  'type' => 'url',
92
  'placeholder' => \_x( 'https://www.facebook.com/YourPersonalProfile', 'Example Facebook Personal URL', 'autodescription' ),
93
  'value' => $this->get_user_option( $user->ID, 'facebook_page' ),
94
  'class' => '',
95
  ],
96
+ $_field_settings->keys['twitter_page'] => (object) [
97
  'name' => \__( 'Twitter profile name', 'autodescription' ),
98
  'type' => 'text',
99
  'placeholder' => \_x( '@your-personal-username', 'Twitter @username', 'autodescription' ),
117
  */
118
  public function _update_user_settings( $user_id ) {
119
 
120
+ if ( empty( $_POST ) ) return;
121
+
122
  \check_admin_referer( 'update-user_' . $user_id );
123
  if ( ! \current_user_can( 'edit_user', $user_id ) ) return;
124
 
 
 
 
125
  $user = new \WP_User( $user_id );
126
 
127
+ if ( ! $user->has_cap( 'publish_posts' ) ) return;
 
128
 
129
  $success = [];
130
  $defaults = $this->get_default_user_data();
131
 
132
+ $_field_settings = $this->get_profile_field_settings();
133
+
134
+ foreach ( $_field_settings->keys as $option => $post_key ) {
135
+ if ( isset( $_POST[ $post_key ] ) ) {
136
+ $value = $this->{$_field_settings->sanitization[ $option ]}( $_POST[ $post_key ] )
137
+ ?: $defaults[ $option ]; // phpcs:ignore, WordPress.WhiteSpace
138
 
139
  $success[] = (bool) $this->update_user_option( $user_id, $option, $value );
140
  }
inc/classes/query.class.php CHANGED
@@ -1,7 +1,8 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -30,7 +31,7 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
30
  *
31
  * @since 2.8.0
32
  */
33
- class Query extends Compat {
34
 
35
  /**
36
  * Checks for pretty permalinks.
@@ -77,6 +78,7 @@ class Query extends Compat {
77
  return false;
78
  }
79
 
 
80
  /**
81
  * Outputs a doing it wrong notice if an error occurs in the current query.
82
  *
@@ -84,7 +86,6 @@ class Query extends Compat {
84
  *
85
  * @param string $method The original caller method.
86
  */
87
- // phpcs:disable -- Method unused in production.
88
  protected function do_query_error_notice( $method ) {
89
 
90
  $message = "You've initiated a method that uses queries too early.";
@@ -98,12 +99,14 @@ class Query extends Compat {
98
  $this->_doing_it_wrong( \esc_html( $method ), \esc_html( $message ), '2.9.0' );
99
 
100
  //* Backtrace debugging.
101
- // $depth = 10;
102
- // if ( $_more ) {
103
- // error_log( var_export( debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, $depth ), true ) );
104
- // $_more = false;
105
- // }
106
- } // phpcs:enable
 
 
107
 
108
  /**
109
  * Returns the post type name from current screen.
@@ -118,6 +121,22 @@ class Query extends Compat {
118
  return isset( $current_screen->post_type ) ? $current_screen->post_type : '';
119
  }
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  /**
122
  * Get the real page ID, also from CPT, archives, author, blog, etc.
123
  *
@@ -130,7 +149,7 @@ class Query extends Compat {
130
  */
131
  public function get_the_real_ID( $use_cache = true ) { // phpcs:ignore -- ID is capitalized because WordPress does that too: get_the_ID().
132
 
133
- if ( $this->is_admin() )
134
  return $this->get_the_real_admin_ID();
135
 
136
  $use_cache = $use_cache && $this->can_cache_query( __METHOD__ );
@@ -146,7 +165,7 @@ class Query extends Compat {
146
  $id = $use_cache ? $this->check_the_real_ID() : 0;
147
 
148
  if ( ! $id ) {
149
- //* This catches most ID's. Even Post IDs.
150
  $id = \get_queried_object_id();
151
  }
152
 
@@ -265,7 +284,7 @@ class Query extends Compat {
265
 
266
  if ( isset( $cache ) ) return $cache;
267
 
268
- $_object = $this->is_admin() ? $GLOBALS['current_screen'] : \get_queried_object();
269
 
270
  return $cache = ! empty( $_object->taxonomy ) ? $_object->taxonomy : '';
271
  }
@@ -296,15 +315,20 @@ class Query extends Compat {
296
  * Detects attachment page.
297
  *
298
  * @since 2.6.0
 
299
  *
300
  * @param mixed $attachment Attachment ID, title, slug, or array of such.
301
  * @return bool
302
  */
303
  public function is_attachment( $attachment = '' ) {
304
 
305
- if ( empty( $attachment ) )
 
 
 
306
  return \is_attachment();
307
 
 
308
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $attachment ) )
309
  return $cache;
310
 
@@ -317,6 +341,19 @@ class Query extends Compat {
317
  return $is_attachment;
318
  }
319
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  /**
321
  * Determines whether the content type is both singular and archival.
322
  * Simply put, it detects a blog page and WooCommerce shop page.
@@ -339,7 +376,7 @@ class Query extends Compat {
339
  */
340
  public function is_archive() {
341
 
342
- if ( $this->is_admin() )
343
  return $this->is_archive_admin();
344
 
345
  if ( \is_archive() && false === $this->is_singular() )
@@ -378,6 +415,7 @@ class Query extends Compat {
378
  */
379
  public function is_term_edit() {
380
 
 
381
  if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
382
  return $cache;
383
 
@@ -431,6 +469,7 @@ class Query extends Compat {
431
  if ( empty( $author ) )
432
  return \is_author();
433
 
 
434
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $author ) )
435
  return $cache;
436
 
@@ -444,7 +483,7 @@ class Query extends Compat {
444
  }
445
 
446
  /**
447
- * Detect the separated blog page.
448
  *
449
  * @since 2.3.4
450
  *
@@ -458,6 +497,7 @@ class Query extends Compat {
458
 
459
  $id = $id ?: $this->get_the_real_ID();
460
 
 
461
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $id ) )
462
  return $cache;
463
 
@@ -483,6 +523,21 @@ class Query extends Compat {
483
  return $is_blog_page;
484
  }
485
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  /**
487
  * Detects category archives.
488
  *
@@ -494,9 +549,10 @@ class Query extends Compat {
494
  */
495
  public function is_category( $category = '' ) {
496
 
497
- if ( $this->is_admin() )
498
  return $this->is_category_admin();
499
 
 
500
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $category ) )
501
  return $cache;
502
 
@@ -514,29 +570,26 @@ class Query extends Compat {
514
  *
515
  * @since 2.6.0
516
  * @since 3.1.0 No longer guesses category by name. It now only matches WordPress' built-in category.
517
- * @global \WP_Screen $current_screen
518
  *
519
  * @return bool Post Type is category
520
  */
521
  public function is_category_admin() {
 
 
522
 
523
- if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
524
- return $cache;
525
-
526
- global $current_screen;
527
-
528
- $is_category = false;
529
-
530
- if ( $this->is_archive_admin() && isset( $current_screen->taxonomy ) ) {
531
- $is_category = 'category' === $current_screen->taxonomy;
532
- }
533
-
534
- $this->set_query_cache(
535
- __METHOD__,
536
- $is_category
537
- );
538
-
539
- return $is_category;
540
  }
541
 
542
  /**
@@ -583,6 +636,7 @@ class Query extends Compat {
583
  */
584
  public function is_real_front_page() {
585
 
 
586
  if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
587
  return $cache;
588
 
@@ -629,25 +683,28 @@ class Query extends Compat {
629
  /**
630
  * Checks for front page by input ID.
631
  *
632
- * Doesn't always return true when the ID is 0, although the homepage might be.
633
- * This is because it checks for the query, to prevent conflicts.
634
- * @see $this->is_real_front_page_by_id().
 
635
  *
636
  * @since 2.9.0
637
  * @since 2.9.3 Now tests for archive and 404 before testing homepage as blog.
638
  * @since 3.2.2: Removed SEO settings page check. This now returns false on that page.
639
  *
640
- * @param int The page ID, required. Can be 0.
641
  * @return bool True if ID if for the homepage.
642
  */
643
  public function is_front_page_by_id( $id ) {
644
 
645
  $id = (int) $id;
646
 
 
647
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $id ) )
648
  return $cache;
649
 
650
  $is_front_page = false;
 
651
  $sof = \get_option( 'show_on_front' );
652
 
653
  //* Compare against $id
@@ -708,6 +765,8 @@ class Query extends Compat {
708
  * When $page is supplied, it will check against the current object. So it will not work in the admin screens.
709
  *
710
  * @since 2.6.0
 
 
711
  * @staticvar bool $cache
712
  * @uses $this->is_singular()
713
  *
@@ -716,18 +775,25 @@ class Query extends Compat {
716
  */
717
  public function is_page( $page = '' ) {
718
 
719
- if ( $this->is_admin() )
720
  return $this->is_page_admin();
721
 
722
  if ( empty( $page ) )
723
  return \is_page();
724
 
 
725
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $page ) )
726
  return $cache;
727
 
 
 
 
 
 
 
728
  $this->set_query_cache(
729
  __METHOD__,
730
- $is_page = \is_page( $page ),
731
  $page
732
  );
733
 
@@ -738,30 +804,43 @@ class Query extends Compat {
738
  * Detects pages within the admin area.
739
  *
740
  * @since 2.6.0
 
741
  * @see $this->is_page()
742
- * @global \WP_Screen $current_screen;
743
  *
744
  * @return bool
745
  */
746
  public function is_page_admin() {
747
- global $current_screen;
748
-
749
- if ( isset( $current_screen->post_type ) && 'page' === $current_screen->post_type )
750
- return true;
751
-
752
- return false;
753
  }
754
 
755
  /**
756
- * Detects preview.
757
  *
758
  * @since 2.6.0
 
 
 
 
 
759
  * @staticvar bool $cache
760
  *
761
  * @return bool
762
  */
763
  public function is_preview() {
764
- return \is_preview();
 
 
 
 
 
 
 
 
 
 
 
 
 
765
  }
766
 
767
  /**
@@ -781,6 +860,7 @@ class Query extends Compat {
781
  * When $post is supplied, it will check against the current object. So it will not work in the admin screens.
782
  *
783
  * @since 2.6.0
 
784
  * @staticvar bool $cache
785
  * @uses The_SEO_Framework_Query::is_single_admin()
786
  *
@@ -789,18 +869,22 @@ class Query extends Compat {
789
  */
790
  public function is_single( $post = '' ) {
791
 
792
- if ( $this->is_admin() )
793
  return $this->is_single_admin();
794
 
795
- if ( empty( $post ) )
796
- return \is_single();
797
-
798
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $post ) )
799
  return $cache;
800
 
 
 
 
 
 
 
801
  $this->set_query_cache(
802
  __METHOD__,
803
- $is_single = \is_single( $post ),
804
  $post
805
  );
806
 
@@ -811,14 +895,14 @@ class Query extends Compat {
811
  * Detects posts within the admin area.
812
  *
813
  * @since 2.6.0
814
- * @global \WP_Screen $current_screen
815
  * @see The_SEO_Framework_Query::is_single()
816
  *
817
  * @return bool
818
  */
819
  public function is_single_admin() {
820
- global $current_screen;
821
- return isset( $current_screen->post_type ) && 'post' === $current_screen->post_type;
822
  }
823
 
824
  /**
@@ -827,42 +911,37 @@ class Query extends Compat {
827
  *
828
  * @since 2.5.2
829
  * @since 3.1.0 Now passes $post_types parameter in admin screens, only when it's an integer.
 
830
  * @uses The_SEO_Framework_Query::is_singular_admin()
831
  * @uses The_SEO_Framework_Query::is_blog_page()
832
  * @uses The_SEO_Framework_Query::is_wc_shop()
833
  *
834
- * @param string|array|int $post_types Optional. Post type or array of post types, or ID of post. Default empty string.
835
  * @return bool Post Type is singular
836
  */
837
  public function is_singular( $post_types = '' ) {
838
 
839
- $id = null;
840
-
841
  if ( is_int( $post_types ) ) {
842
- //* Cache ID. Core is_singular() doesn't accept integers.
843
- $id = $post_types;
844
  $post_types = '';
845
  }
846
 
847
  //* WP_Query functions require loop, do alternative check.
848
- if ( $this->is_admin() )
849
- return $this->is_singular_admin( $id );
850
 
851
- if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $post_types, $id ) )
852
- return $cache;
853
 
854
- if ( ! $is_singular = \is_singular( $post_types ) ) {
855
- $id = isset( $id ) ? $id : $this->get_the_real_ID();
 
856
 
857
- //* Check for somewhat singulars. We need this to adjust Meta data filled in Posts.
858
- if ( $this->is_blog_page( $id ) || $this->is_wc_shop() )
859
- $is_singular = true;
860
- }
861
 
862
  $this->set_query_cache(
863
  __METHOD__,
864
- $is_singular,
865
- $post_types, $id
866
  );
867
 
868
  return $is_singular;
@@ -873,22 +952,14 @@ class Query extends Compat {
873
  *
874
  * @since 2.5.2
875
  * @since 3.1.0 Added $post_id parameter. When used, it'll only check for it.
 
876
  * @global \WP_Screen $current_screen
877
  *
878
- * @param null|int $post_id The post ID.
879
  * @return bool Post Type is singular
880
  */
881
- public function is_singular_admin( $post_id = null ) {
882
-
883
- if ( isset( $post_id ) ) {
884
- $post = \get_post( $post_id );
885
- return $post && $post instanceof \WP_Post;
886
- } else {
887
- global $current_screen;
888
- return isset( $current_screen->base ) && in_array( $current_screen->base, [ 'edit', 'post' ], true );
889
- }
890
-
891
- return false;
892
  }
893
 
894
  /**
@@ -920,9 +991,10 @@ class Query extends Compat {
920
  public function is_tag( $tag = '' ) {
921
 
922
  //* Admin requires another check.
923
- if ( $this->is_admin() )
924
  return $this->is_tag_admin();
925
 
 
926
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $tag ) )
927
  return $cache;
928
 
@@ -940,35 +1012,19 @@ class Query extends Compat {
940
  *
941
  * @since 2.6.0
942
  * @since 3.1.0 No longer guesses tag by name. It now only matches WordPress' built-in tag.
943
- * @global \WP_Screen $current_screen
944
  *
945
  * @return bool Post Type is tag.
946
  */
947
  public function is_tag_admin() {
948
-
949
- if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
950
- return $cache;
951
-
952
- global $current_screen;
953
-
954
- $is_tag = false;
955
-
956
- if ( $this->is_archive_admin() && isset( $current_screen->taxonomy ) ) {
957
- $is_tag = 'post_tag' === $current_screen->taxonomy;
958
- }
959
-
960
- $this->set_query_cache(
961
- __METHOD__,
962
- $is_tag
963
- );
964
-
965
- return $is_tag;
966
  }
967
 
968
  /**
969
  * Detects taxonomy archives.
970
  *
971
  * @since 2.6.0
 
972
  *
973
  * @param string|array $taxonomy Optional. Taxonomy slug or slugs.
974
  * @param int|string|array $term Optional. Term ID, name, slug or array of Term IDs, names, and slugs.
@@ -976,13 +1032,15 @@ class Query extends Compat {
976
  */
977
  public function is_tax( $taxonomy = '', $term = '' ) {
978
 
 
979
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $taxonomy, $term ) )
980
  return $cache;
981
 
982
  $this->set_query_cache(
983
  __METHOD__,
984
  $is_tax = \is_tax( $taxonomy, $term ),
985
- $taxonomy, $term
 
986
  );
987
 
988
  return $is_tax;
@@ -998,12 +1056,13 @@ class Query extends Compat {
998
  */
999
  public function is_wc_shop() {
1000
 
 
1001
  if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1002
  return $cache;
1003
 
1004
  $this->set_query_cache(
1005
  __METHOD__,
1006
- $is_shop = false === $this->is_admin() && function_exists( 'is_shop' ) && \is_shop()
1007
  );
1008
 
1009
  return $is_shop;
@@ -1013,22 +1072,49 @@ class Query extends Compat {
1013
  * Determines if the page is the WooCommerce plugin Product page.
1014
  *
1015
  * @since 2.5.2
 
 
1016
  *
 
1017
  * @return bool True if on a WooCommerce Product page.
1018
  */
1019
- public function is_wc_product() {
1020
 
1021
- if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
 
 
 
 
1022
  return $cache;
1023
 
 
 
 
 
 
 
1024
  $this->set_query_cache(
1025
  __METHOD__,
1026
- $is_product = false === $this->is_admin() && function_exists( 'is_product' ) && \is_product()
 
1027
  );
1028
 
1029
  return $is_product;
1030
  }
1031
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1032
  /**
1033
  * Detects year archives.
1034
  *
@@ -1049,7 +1135,9 @@ class Query extends Compat {
1049
  * @return bool True if SSL, false otherwise.
1050
  */
1051
  public function is_ssl() {
 
1052
  static $cache = null;
 
1053
  return isset( $cache ) ? $cache : $cache = \is_ssl();
1054
  }
1055
 
@@ -1067,12 +1155,13 @@ class Query extends Compat {
1067
  */
1068
  public function is_seo_settings_page( $secure = true ) {
1069
 
1070
- if ( ! $this->is_admin() )
1071
  return false;
1072
 
1073
  if ( ! $secure )
1074
  return $this->is_menu_page( $this->seo_settings_page_hook, $this->seo_settings_page_slug );
1075
 
 
1076
  if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1077
  return $cache;
1078
 
@@ -1111,8 +1200,11 @@ class Query extends Compat {
1111
 
1112
  if ( isset( $page_hook ) ) {
1113
  return $page_hook === $pagehook;
1114
- } elseif ( $this->is_admin() && $pageslug ) {
1115
- return ! empty( $_GET['page'] ) && $pageslug === $_GET['page']; // CSRF, input var OK.
 
 
 
1116
  }
1117
 
1118
  return false;
@@ -1130,6 +1222,7 @@ class Query extends Compat {
1130
  */
1131
  public function page() {
1132
 
 
1133
  if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1134
  return $cache;
1135
 
@@ -1164,6 +1257,7 @@ class Query extends Compat {
1164
  */
1165
  public function paged() {
1166
 
 
1167
  if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1168
  return $cache;
1169
 
@@ -1201,14 +1295,20 @@ class Query extends Compat {
1201
  */
1202
  public function numpages() {
1203
 
 
1204
  if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1205
  return $cache;
1206
 
1207
- if ( $this->is_admin() ) return 1;
 
 
 
 
1208
 
1209
  global $wp_query;
1210
 
1211
  $post = null;
 
1212
  if ( $this->is_singular() && ! $this->is_singular_archive() )
1213
  $post = \get_post( $this->get_the_real_ID() );
1214
 
@@ -1243,6 +1343,9 @@ class Query extends Compat {
1243
  $numpages = count( $_pages );
1244
  } elseif ( isset( $wp_query->max_num_pages ) ) {
1245
  $numpages = (int) $wp_query->max_num_pages;
 
 
 
1246
  }
1247
 
1248
  $this->set_query_cache( __METHOD__, $numpages );
@@ -1268,11 +1371,19 @@ class Query extends Compat {
1268
  * Determines whether we're on The SEO Framework's sitemap or not.
1269
  *
1270
  * @since 2.9.2
 
 
1271
  *
 
1272
  * @return bool
1273
  */
1274
- public function is_sitemap() {
1275
- return (bool) $this->doing_sitemap;
 
 
 
 
 
1276
  }
1277
 
1278
  /**
@@ -1296,9 +1407,7 @@ class Query extends Compat {
1296
  *
1297
  * @param string $method The method that wants to cache, used as the key to set or get.
1298
  * @param mixed $value_to_set The value to set.
1299
- * @param array|mixed $hash Extra arguments, that will be used to generate an alternative cache key.
1300
- * Must always be inside a single array when $value_to_set is set. @see $this->set_query_cache()
1301
- * Must always be separated parameters otherwise.
1302
  * @return mixed : {
1303
  * mixed The cached value if set and $value_to_set is null.
1304
  * null If the query can't be cached yet, or when no value has been set.
@@ -1308,7 +1417,7 @@ class Query extends Compat {
1308
  * }
1309
  * }
1310
  */
1311
- public function get_query_cache( $method, $value_to_set = null ) {
1312
 
1313
  static $can_cache_query = null;
1314
 
@@ -1322,9 +1431,9 @@ class Query extends Compat {
1322
 
1323
  static $cache = [];
1324
 
1325
- if ( func_num_args() > 2 ) {
1326
- // phpcs:ignore -- No objects are inserted, nor is this ever unserialized.
1327
- $hash = isset( $value_to_set ) ? serialize( (array) func_get_arg( 2 ) ) : serialize( array_slice( func_get_args(), 2 ) );
1328
  } else {
1329
  $hash = false;
1330
  }
@@ -1352,17 +1461,13 @@ class Query extends Compat {
1352
  *
1353
  * @param string $method The method that wants to set. Used as a caching key.
1354
  * @param mixed $value_to_set If null, no cache will be set.
1355
- * @param mixed $hash Extra arguments, that will be used to generate an alternative cache key.
1356
  * @return bool : {
1357
  * true If the value is being set for the first time.
1358
  * false If the value has been set and $value_to_set is being overwritten.
1359
  * }
1360
  */
1361
- public function set_query_cache( $method, $value_to_set ) {
1362
- if ( func_num_args() > 2 ) {
1363
- return $this->get_query_cache( $method, $value_to_set, array_slice( func_get_args(), 2 ) );
1364
- } else {
1365
- return $this->get_query_cache( $method, $value_to_set );
1366
- }
1367
  }
1368
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Query
4
  */
5
+
6
  namespace The_SEO_Framework;
7
 
8
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
31
  *
32
  * @since 2.8.0
33
  */
34
+ class Query extends Core {
35
 
36
  /**
37
  * Checks for pretty permalinks.
78
  return false;
79
  }
80
 
81
+ // phpcs:disable -- method unused in production.
82
  /**
83
  * Outputs a doing it wrong notice if an error occurs in the current query.
84
  *
86
  *
87
  * @param string $method The original caller method.
88
  */
 
89
  protected function do_query_error_notice( $method ) {
90
 
91
  $message = "You've initiated a method that uses queries too early.";
99
  $this->_doing_it_wrong( \esc_html( $method ), \esc_html( $message ), '2.9.0' );
100
 
101
  //* Backtrace debugging.
102
+ $depth = 10;
103
+ static $_more = true;
104
+ if ( $_more ) {
105
+ error_log( var_export( @debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, $depth ), true ) );
106
+ $_more = false;
107
+ }
108
+ }
109
+ // phpcs:enable -- Method unused in production.
110
 
111
  /**
112
  * Returns the post type name from current screen.
121
  return isset( $current_screen->post_type ) ? $current_screen->post_type : '';
122
  }
123
 
124
+ /**
125
+ * Returns a list of post types shared with the taxonomy.
126
+ *
127
+ * @since 4.0.0
128
+ *
129
+ * @param string $taxonomy Optional. The taxonomy to check. Defaults to current screen/query taxonomy.
130
+ * @return array List of post types.
131
+ */
132
+ public function get_post_types_from_taxonomy( $taxonomy = '' ) {
133
+
134
+ $taxonomy = $taxonomy ?: $this->get_current_taxonomy();
135
+ $tax = $taxonomy ? \get_taxonomy( $taxonomy ) : null;
136
+
137
+ return ! empty( $tax->object_type ) ? $tax->object_type : [];
138
+ }
139
+
140
  /**
141
  * Get the real page ID, also from CPT, archives, author, blog, etc.
142
  *
149
  */
150
  public function get_the_real_ID( $use_cache = true ) { // phpcs:ignore -- ID is capitalized because WordPress does that too: get_the_ID().
151
 
152
+ if ( \is_admin() )
153
  return $this->get_the_real_admin_ID();
154
 
155
  $use_cache = $use_cache && $this->can_cache_query( __METHOD__ );
165
  $id = $use_cache ? $this->check_the_real_ID() : 0;
166
 
167
  if ( ! $id ) {
168
+ //* This catches most IDs. Even Post IDs.
169
  $id = \get_queried_object_id();
170
  }
171
 
284
 
285
  if ( isset( $cache ) ) return $cache;
286
 
287
+ $_object = \is_admin() ? $GLOBALS['current_screen'] : \get_queried_object();
288
 
289
  return $cache = ! empty( $_object->taxonomy ) ? $_object->taxonomy : '';
290
  }
315
  * Detects attachment page.
316
  *
317
  * @since 2.6.0
318
+ * @since 4.0.0 Now reliably works on admin screens.
319
  *
320
  * @param mixed $attachment Attachment ID, title, slug, or array of such.
321
  * @return bool
322
  */
323
  public function is_attachment( $attachment = '' ) {
324
 
325
+ if ( \is_admin() )
326
+ return $this->is_attachment_admin();
327
+
328
+ if ( ! $attachment )
329
  return \is_attachment();
330
 
331
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
332
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $attachment ) )
333
  return $cache;
334
 
341
  return $is_attachment;
342
  }
343
 
344
+ /**
345
+ * Detects attachments within the admin area.
346
+ *
347
+ * @since 4.0.0
348
+ * @see $this->is_attachment()
349
+ * @global \WP_Screen $current_screen;
350
+ *
351
+ * @return bool
352
+ */
353
+ public function is_attachment_admin() {
354
+ return $this->is_singular_admin() && 'attachment' === $this->get_admin_post_type();
355
+ }
356
+
357
  /**
358
  * Determines whether the content type is both singular and archival.
359
  * Simply put, it detects a blog page and WooCommerce shop page.
376
  */
377
  public function is_archive() {
378
 
379
+ if ( \is_admin() )
380
  return $this->is_archive_admin();
381
 
382
  if ( \is_archive() && false === $this->is_singular() )
415
  */
416
  public function is_term_edit() {
417
 
418
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
419
  if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
420
  return $cache;
421
 
469
  if ( empty( $author ) )
470
  return \is_author();
471
 
472
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
473
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $author ) )
474
  return $cache;
475
 
483
  }
484
 
485
  /**
486
+ * Detect the non-home blog page by query (ID).
487
  *
488
  * @since 2.3.4
489
  *
497
 
498
  $id = $id ?: $this->get_the_real_ID();
499
 
500
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
501
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $id ) )
502
  return $cache;
503
 
523
  return $is_blog_page;
524
  }
525
 
526
+ /**
527
+ * Checks blog page by sole ID.
528
+ *
529
+ * @since 4.0.0
530
+ *
531
+ * @param int $id The ID to check
532
+ * @return bool
533
+ */
534
+ public function is_blog_page_by_id( $id ) {
535
+
536
+ $pfp = (int) \get_option( 'page_for_posts' );
537
+
538
+ return 0 !== $pfp && $id === $pfp;
539
+ }
540
+
541
  /**
542
  * Detects category archives.
543
  *
549
  */
550
  public function is_category( $category = '' ) {
551
 
552
+ if ( \is_admin() )
553
  return $this->is_category_admin();
554
 
555
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
556
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $category ) )
557
  return $cache;
558
 
570
  *
571
  * @since 2.6.0
572
  * @since 3.1.0 No longer guesses category by name. It now only matches WordPress' built-in category.
573
+ * @since 4.0.0 Removed caching.
574
  *
575
  * @return bool Post Type is category
576
  */
577
  public function is_category_admin() {
578
+ return $this->is_archive_admin() && 'category' === $this->get_current_taxonomy();
579
+ }
580
 
581
+ /**
582
+ * Detects customizer preview.
583
+ *
584
+ * Unlike is_preview(), WordPress has prior security checks for this
585
+ * in `\WP_Customize_Manager::setup_theme()`.
586
+ *
587
+ * @since 4.0.0
588
+ *
589
+ * @return bool
590
+ */
591
+ public function is_customize_preview() {
592
+ return \is_customize_preview();
 
 
 
 
 
593
  }
594
 
595
  /**
636
  */
637
  public function is_real_front_page() {
638
 
639
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
640
  if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
641
  return $cache;
642
 
683
  /**
684
  * Checks for front page by input ID.
685
  *
686
+ * NOTE: Doesn't always return true when the ID is 0, although the homepage might be.
687
+ * This is because it checks for the query, to prevent conflicts.
688
+ *
689
+ * @see $this->is_real_front_page_by_id(); Alternative to NOTE above.
690
  *
691
  * @since 2.9.0
692
  * @since 2.9.3 Now tests for archive and 404 before testing homepage as blog.
693
  * @since 3.2.2: Removed SEO settings page check. This now returns false on that page.
694
  *
695
+ * @param int $id The page ID, required. Can be 0.
696
  * @return bool True if ID if for the homepage.
697
  */
698
  public function is_front_page_by_id( $id ) {
699
 
700
  $id = (int) $id;
701
 
702
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
703
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $id ) )
704
  return $cache;
705
 
706
  $is_front_page = false;
707
+
708
  $sof = \get_option( 'show_on_front' );
709
 
710
  //* Compare against $id
765
  * When $page is supplied, it will check against the current object. So it will not work in the admin screens.
766
  *
767
  * @since 2.6.0
768
+ * @since 4.0.0 Now tests for post type, which is more reliable.
769
+ * @ignore not used internally, polar opposite of is_single().
770
  * @staticvar bool $cache
771
  * @uses $this->is_singular()
772
  *
775
  */
776
  public function is_page( $page = '' ) {
777
 
778
+ if ( \is_admin() )
779
  return $this->is_page_admin();
780
 
781
  if ( empty( $page ) )
782
  return \is_page();
783
 
784
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
785
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $page ) )
786
  return $cache;
787
 
788
+ if ( is_int( $page ) || $page instanceof \WP_Post ) {
789
+ $is_page = in_array( \get_post_type( $page ), $this->get_hierarchical_post_types(), true );
790
+ } else {
791
+ $is_page = \is_page( $page );
792
+ }
793
+
794
  $this->set_query_cache(
795
  __METHOD__,
796
+ $is_page,
797
  $page
798
  );
799
 
804
  * Detects pages within the admin area.
805
  *
806
  * @since 2.6.0
807
+ * @since 4.0.0 Now tests for post type, although redundant.
808
  * @see $this->is_page()
 
809
  *
810
  * @return bool
811
  */
812
  public function is_page_admin() {
813
+ return $this->is_singular_admin() && in_array( $this->get_admin_post_type(), $this->get_hierarchical_post_types(), true );
 
 
 
 
 
814
  }
815
 
816
  /**
817
+ * Detects preview, securely.
818
  *
819
  * @since 2.6.0
820
+ * @since 4.0.0 This is now deemed a secure method.
821
+ * 1. Added is_user_logged_in() check.
822
+ * 2. Added is_singular() check, so get_the_ID() won't cross with blog pages.
823
+ * 3. Added current_user_can() check.
824
+ * 4. Added wp_verify_nonce() check.
825
  * @staticvar bool $cache
826
  *
827
  * @return bool
828
  */
829
  public function is_preview() {
830
+
831
+ $is_preview = false;
832
+
833
+ if ( \is_preview()
834
+ && \is_user_logged_in()
835
+ && \is_singular()
836
+ && \current_user_can( 'edit_post', \get_the_ID() )
837
+ && isset( $_GET['preview_id'], $_GET['preview_nonce'] )
838
+ && \wp_verify_nonce( $_GET['preview_nonce'], 'post_preview_' . (int) $_GET['preview_id'] )
839
+ ) {
840
+ $is_preview = true;
841
+ }
842
+
843
+ return $is_preview;
844
  }
845
 
846
  /**
860
  * When $post is supplied, it will check against the current object. So it will not work in the admin screens.
861
  *
862
  * @since 2.6.0
863
+ * @since 4.0.0 Now tests for post type, which is more reliable.
864
  * @staticvar bool $cache
865
  * @uses The_SEO_Framework_Query::is_single_admin()
866
  *
869
  */
870
  public function is_single( $post = '' ) {
871
 
872
+ if ( \is_admin() )
873
  return $this->is_single_admin();
874
 
875
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
 
 
876
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $post ) )
877
  return $cache;
878
 
879
+ if ( is_int( $post ) || $post instanceof \WP_Post ) {
880
+ $is_single = in_array( \get_post_type( $post ), $this->get_nonhierarchical_post_types(), true );
881
+ } else {
882
+ $is_single = \is_single( $post );
883
+ }
884
+
885
  $this->set_query_cache(
886
  __METHOD__,
887
+ $is_single,
888
  $post
889
  );
890
 
895
  * Detects posts within the admin area.
896
  *
897
  * @since 2.6.0
898
+ * @since 4.0.0 Now no longer returns true on categories and tags.
899
  * @see The_SEO_Framework_Query::is_single()
900
  *
901
  * @return bool
902
  */
903
  public function is_single_admin() {
904
+ // Checks for "is_singular_admin()" because the post type is non-hierarchical.
905
+ return $this->is_singular_admin() && in_array( $this->get_admin_post_type(), $this->get_nonhierarchical_post_types(), true );
906
  }
907
 
908
  /**
911
  *
912
  * @since 2.5.2
913
  * @since 3.1.0 Now passes $post_types parameter in admin screens, only when it's an integer.
914
+ * @since 4.0.0 No longer processes integers as input.
915
  * @uses The_SEO_Framework_Query::is_singular_admin()
916
  * @uses The_SEO_Framework_Query::is_blog_page()
917
  * @uses The_SEO_Framework_Query::is_wc_shop()
918
  *
919
+ * @param string|array $post_types Optional. Post type or array of post types. Default empty string.
920
  * @return bool Post Type is singular
921
  */
922
  public function is_singular( $post_types = '' ) {
923
 
 
 
924
  if ( is_int( $post_types ) ) {
925
+ // Integers are no longer accepted.
 
926
  $post_types = '';
927
  }
928
 
929
  //* WP_Query functions require loop, do alternative check.
930
+ if ( \is_admin() )
931
+ return $this->is_singular_admin();
932
 
933
+ if ( $post_types )
934
+ return \is_singular( $post_types );
935
 
936
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
937
+ if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
938
+ return $cache;
939
 
940
+ $is_singular = \is_singular() || $this->is_singular_archive();
 
 
 
941
 
942
  $this->set_query_cache(
943
  __METHOD__,
944
+ $is_singular
 
945
  );
946
 
947
  return $is_singular;
952
  *
953
  * @since 2.5.2
954
  * @since 3.1.0 Added $post_id parameter. When used, it'll only check for it.
955
+ * @since 4.0.0 Removed first parameter.
956
  * @global \WP_Screen $current_screen
957
  *
 
958
  * @return bool Post Type is singular
959
  */
960
+ public function is_singular_admin() {
961
+ global $current_screen;
962
+ return isset( $current_screen->base ) && in_array( $current_screen->base, [ 'edit', 'post' ], true );
 
 
 
 
 
 
 
 
963
  }
964
 
965
  /**
991
  public function is_tag( $tag = '' ) {
992
 
993
  //* Admin requires another check.
994
+ if ( \is_admin() )
995
  return $this->is_tag_admin();
996
 
997
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
998
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $tag ) )
999
  return $cache;
1000
 
1012
  *
1013
  * @since 2.6.0
1014
  * @since 3.1.0 No longer guesses tag by name. It now only matches WordPress' built-in tag.
1015
+ * @since 4.0.0 Removed caching.
1016
  *
1017
  * @return bool Post Type is tag.
1018
  */
1019
  public function is_tag_admin() {
1020
+ return $this->is_archive_admin() && 'post_tag' === $this->get_current_taxonomy();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1021
  }
1022
 
1023
  /**
1024
  * Detects taxonomy archives.
1025
  *
1026
  * @since 2.6.0
1027
+ * @TODO add is_tax_admin() ?
1028
  *
1029
  * @param string|array $taxonomy Optional. Taxonomy slug or slugs.
1030
  * @param int|string|array $term Optional. Term ID, name, slug or array of Term IDs, names, and slugs.
1032
  */
1033
  public function is_tax( $taxonomy = '', $term = '' ) {
1034
 
1035
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1036
  if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $taxonomy, $term ) )
1037
  return $cache;
1038
 
1039
  $this->set_query_cache(
1040
  __METHOD__,
1041
  $is_tax = \is_tax( $taxonomy, $term ),
1042
+ $taxonomy,
1043
+ $term
1044
  );
1045
 
1046
  return $is_tax;
1056
  */
1057
  public function is_wc_shop() {
1058
 
1059
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1060
  if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1061
  return $cache;
1062
 
1063
  $this->set_query_cache(
1064
  __METHOD__,
1065
+ $is_shop = ! \is_admin() && function_exists( 'is_shop' ) && \is_shop()
1066
  );
1067
 
1068
  return $is_shop;
1072
  * Determines if the page is the WooCommerce plugin Product page.
1073
  *
1074
  * @since 2.5.2
1075
+ * @since 4.0.0 : 1. Added admin support.
1076
+ * 2. Added parameter for the Post ID or post to test.
1077
  *
1078
+ * @param int|\WP_Post $post When set, checks if the post is of type product.
1079
  * @return bool True if on a WooCommerce Product page.
1080
  */
1081
+ public function is_wc_product( $post = 0 ) {
1082
 
1083
+ if ( \is_admin() )
1084
+ return $this->is_wc_product_admin();
1085
+
1086
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1087
+ if ( null !== $cache = $this->get_query_cache( __METHOD__, null, $post ) )
1088
  return $cache;
1089
 
1090
+ if ( $post ) {
1091
+ $is_product = 'product' === \get_post_type( $post );
1092
+ } else {
1093
+ $is_product = function_exists( 'is_product' ) && \is_product();
1094
+ }
1095
+
1096
  $this->set_query_cache(
1097
  __METHOD__,
1098
+ $is_product,
1099
+ $post
1100
  );
1101
 
1102
  return $is_product;
1103
  }
1104
 
1105
+ /**
1106
+ * Detects products within the admin area.
1107
+ *
1108
+ * @since 4.0.0
1109
+ * @see The_SEO_Framework_Query::is_wc_product()
1110
+ *
1111
+ * @return bool
1112
+ */
1113
+ public function is_wc_product_admin() {
1114
+ // Checks for "is_singular_admin()" because the post type is non-hierarchical.
1115
+ return $this->is_singular_admin() && 'product' === $this->get_admin_post_type();
1116
+ }
1117
+
1118
  /**
1119
  * Detects year archives.
1120
  *
1135
  * @return bool True if SSL, false otherwise.
1136
  */
1137
  public function is_ssl() {
1138
+
1139
  static $cache = null;
1140
+
1141
  return isset( $cache ) ? $cache : $cache = \is_ssl();
1142
  }
1143
 
1155
  */
1156
  public function is_seo_settings_page( $secure = true ) {
1157
 
1158
+ if ( ! \is_admin() )
1159
  return false;
1160
 
1161
  if ( ! $secure )
1162
  return $this->is_menu_page( $this->seo_settings_page_hook, $this->seo_settings_page_slug );
1163
 
1164
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1165
  if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1166
  return $cache;
1167
 
1200
 
1201
  if ( isset( $page_hook ) ) {
1202
  return $page_hook === $pagehook;
1203
+ } elseif ( \is_admin() && $pageslug ) {
1204
+ // N.B. $_GET['page'] === $plugin_page after admin_init...
1205
+
1206
+ // phpcs:ignore, WordPress.Security.NonceVerification -- This is a public variable, no data is processed.
1207
+ return ! empty( $_GET['page'] ) && $pageslug === $_GET['page'];
1208
  }
1209
 
1210
  return false;
1222
  */
1223
  public function page() {
1224
 
1225
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1226
  if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1227
  return $cache;
1228
 
1257
  */
1258
  public function paged() {
1259
 
1260
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1261
  if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1262
  return $cache;
1263
 
1295
  */
1296
  public function numpages() {
1297
 
1298
+ // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition
1299
  if ( null !== $cache = $this->get_query_cache( __METHOD__ ) )
1300
  return $cache;
1301
 
1302
+ if ( \is_admin() ) {
1303
+ $numpages = 1;
1304
+ $this->set_query_cache( __METHOD__, $numpages );
1305
+ return $numpages;
1306
+ }
1307
 
1308
  global $wp_query;
1309
 
1310
  $post = null;
1311
+
1312
  if ( $this->is_singular() && ! $this->is_singular_archive() )
1313
  $post = \get_post( $this->get_the_real_ID() );
1314
 
1343
  $numpages = count( $_pages );
1344
  } elseif ( isset( $wp_query->max_num_pages ) ) {
1345
  $numpages = (int) $wp_query->max_num_pages;
1346
+ } else {
1347
+ // Empty or faulty query, bail.
1348
+ $numpages = 0;
1349
  }
1350
 
1351
  $this->set_query_cache( __METHOD__, $numpages );
1371
  * Determines whether we're on The SEO Framework's sitemap or not.
1372
  *
1373
  * @since 2.9.2
1374
+ * @since 4.0.0 Now uses static variables instead of class properties.
1375
+ * @staticvar bool $doing_sitemap
1376
  *
1377
+ * @param bool $set Whether to set "doing sitemap".
1378
  * @return bool
1379
  */
1380
+ public function is_sitemap( $set = false ) {
1381
+
1382
+ static $doing_sitemap = false;
1383
+
1384
+ if ( $set ) $doing_sitemap = true;
1385
+
1386
+ return $doing_sitemap;
1387
  }
1388
 
1389
  /**
1407
  *
1408
  * @param string $method The method that wants to cache, used as the key to set or get.
1409
  * @param mixed $value_to_set The value to set.
1410
+ * @param mixed ...$hash Extra arguments, that will are used to differentiaty queries.
 
 
1411
  * @return mixed : {
1412
  * mixed The cached value if set and $value_to_set is null.
1413
  * null If the query can't be cached yet, or when no value has been set.
1417
  * }
1418
  * }
1419
  */
1420
+ public function get_query_cache( $method, $value_to_set = null, ...$hash ) {
1421
 
1422
  static $can_cache_query = null;
1423
 
1431
 
1432
  static $cache = [];
1433
 
1434
+ if ( $hash ) {
1435
+ // phpcs:ignore, WordPress.PHP.DiscouragedPHPFunctions -- No objects are inserted, nor is this ever unserialized.
1436
+ $hash = serialize( $hash );
1437
  } else {
1438
  $hash = false;
1439
  }
1461
  *
1462
  * @param string $method The method that wants to set. Used as a caching key.
1463
  * @param mixed $value_to_set If null, no cache will be set.
1464
+ * @param mixed ...$hash Extra arguments, that will be used to generate an alternative cache key.
1465
  * @return bool : {
1466
  * true If the value is being set for the first time.
1467
  * false If the value has been set and $value_to_set is being overwritten.
1468
  * }
1469
  */
1470
+ public function set_query_cache( $method, $value_to_set, ...$hash ) {
1471
+ return $this->get_query_cache( $method, $value_to_set, ...$hash );
 
 
 
 
1472
  }
1473
  }
inc/classes/render.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -45,9 +47,10 @@ class Render extends Admin_Init {
45
  * @return string The document title
46
  */
47
  public function get_document_title( $title = '' ) {
48
- if ( $this->is_feed() || $this->is_post_type_disabled() ) {
 
49
  return $title;
50
- }
51
  /**
52
  * @since 3.1.0
53
  * @param string $title The generated title.
@@ -69,15 +72,17 @@ class Render extends Admin_Init {
69
  * Use the_seo_framework()->get_title() instead.
70
  *
71
  * @since 3.1.0
 
72
  * @see $this->get_title()
73
  *
74
- * @param string $title The filterable title.
75
  * @return string $title
76
  */
77
- public function get_wp_title( $title = '', $sep = '', $seplocation = '' ) {
78
- if ( $this->is_feed() || $this->is_post_type_disabled() ) {
 
79
  return $title;
80
- }
81
  /**
82
  * @since 3.1.0
83
  * @param string $title The generated title.
@@ -94,19 +99,25 @@ class Render extends Admin_Init {
94
 
95
  /**
96
  * Caches current Image URL in static variable.
97
- * Must be called inside the loop.
98
  *
99
  * @since 2.2.2
100
  * @since 2.7.0 $get_id parameter has been added.
 
101
  * @staticvar string $cache
102
  *
103
  * @return string The image URL.
104
  */
105
  public function get_image_from_cache() {
106
 
107
- static $cache = null;
 
 
 
 
 
108
 
109
- return isset( $cache ) ? $cache : $cache = $this->get_social_image( [], true );
110
  }
111
 
112
  /**
@@ -288,96 +299,26 @@ class Render extends Admin_Init {
288
  */
289
  public function og_image() {
290
 
291
- if ( ! $this->use_og_tags() )
292
- return '';
293
-
294
- $id = $this->get_the_real_ID();
295
-
296
- /**
297
- * @NOTE: Use of this might cause incorrect meta since other functions
298
- * depend on the image from cache.
299
- * @since 2.3.0
300
- * @since 2.7.0 Added output within filter.
301
- * @param string $image The social image URL.
302
- * @param int $id The page or term ID.
303
- */
304
- $image = \apply_filters_ref_array(
305
- 'the_seo_framework_ogimage_output',
306
- [
307
- $this->get_image_from_cache(),
308
- $id,
309
- ]
310
- );
311
 
312
  $output = '';
313
 
314
- /**
315
- * Now returns empty string on false.
316
- * @since 2.6.0
317
- */
318
- if ( $image ) {
319
 
320
- $image = (string) $image;
 
321
 
322
- /**
323
- * Always output
324
- * @since 2.1.1
325
- */
326
- $output .= '<meta property="og:image" content="' . \esc_attr( $image ) . '" />' . "\r\n";
327
-
328
- if ( $image ) {
329
- if ( ! empty( $this->image_dimensions[ $id ]['width'] ) && ! empty( $this->image_dimensions[ $id ]['height'] ) ) {
330
- $output .= '<meta property="og:image:width" content="' . \esc_attr( $this->image_dimensions[ $id ]['width'] ) . '" />' . "\r\n";
331
- $output .= '<meta property="og:image:height" content="' . \esc_attr( $this->image_dimensions[ $id ]['height'] ) . '" />' . "\r\n";
332
- }
333
  }
334
- }
335
-
336
- return $output . $this->render_woocommerce_product_og_image();
337
- }
338
-
339
- /**
340
- * Renders WooCommerce Product Gallery OG images.
341
- *
342
- * @since 2.6.0
343
- * @since 2.7.0 : Added image dimensions if found.
344
- * @since 2.8.0 : Checks for featured ID internally, rather than using a far-off cache.
345
- * @TODO move this to wc compat file.
346
- *
347
- * @return string The rendered OG Image.
348
- */
349
- public function render_woocommerce_product_og_image() {
350
-
351
- $output = '';
352
-
353
- if ( $this->is_wc_product() ) {
354
 
355
- $images = $this->get_image_from_woocommerce_gallery();
356
-
357
- if ( $images && is_array( $images ) ) {
358
-
359
- $post_id = $this->get_the_real_ID();
360
- $post_manual_og = $this->get_custom_field( '_social_image_id', $post_id );
361
- $featured_id = $post_manual_og ? (int) $post_manual_og : (int) \get_post_thumbnail_id( $post_id );
362
-
363
- foreach ( $images as $id ) {
364
-
365
- if ( $id === $featured_id )
366
- continue;
367
-
368
- //* Parse 4096px url.
369
- $img = $this->parse_og_image( $id, [], true );
370
-
371
- if ( $img ) {
372
- $output .= '<meta property="og:image" content="' . \esc_attr( $img ) . '" />' . "\r\n";
373
-
374
- if ( ! empty( $this->image_dimensions[ $id ]['width'] ) && ! empty( $this->image_dimensions[ $id ]['height'] ) ) {
375
- $output .= '<meta property="og:image:width" content="' . \esc_attr( $this->image_dimensions[ $id ]['width'] ) . '" />' . "\r\n";
376
- $output .= '<meta property="og:image:height" content="' . \esc_attr( $this->image_dimensions[ $id ]['height'] ) . '" />' . "\r\n";
377
- }
378
- }
379
- }
380
  }
 
 
 
381
  }
382
 
383
  return $output;
@@ -393,8 +334,7 @@ class Render extends Admin_Init {
393
  */
394
  public function og_sitename() {
395
 
396
- if ( ! $this->use_og_tags() )
397
- return '';
398
 
399
  /**
400
  * @since 2.3.0
@@ -613,34 +553,24 @@ class Render extends Admin_Init {
613
  */
614
  public function twitter_image() {
615
 
616
- if ( ! $this->use_twitter_tags() )
617
- return '';
618
-
619
- $id = $this->get_the_real_ID();
620
-
621
- /**
622
- * @since 2.3.0
623
- * @since 2.7.0 Added output within filter.
624
- * @param string $image The generated Twitter image URL.
625
- * @param int $id The current page or term ID.
626
- */
627
- $image = (string) \apply_filters_ref_array(
628
- 'the_seo_framework_twitterimage_output',
629
- [
630
- $this->get_image_from_cache(),
631
- $id,
632
- ]
633
- );
634
 
635
  $output = '';
636
 
637
- if ( $image ) {
638
- $output = '<meta name="twitter:image" content="' . \esc_attr( $image ) . '" />' . "\r\n";
 
 
 
 
 
639
 
640
- if ( ! empty( $this->image_dimensions[ $id ]['width'] ) && ! empty( $this->image_dimensions[ $id ]['height'] ) ) {
641
- $output .= '<meta name="twitter:image:width" content="' . \esc_attr( $this->image_dimensions[ $id ]['width'] ) . '" />' . "\r\n";
642
- $output .= '<meta name="twitter:image:height" content="' . \esc_attr( $this->image_dimensions[ $id ]['height'] ) . '" />' . "\r\n";
643
  }
 
 
 
644
  }
645
 
646
  return $output;
@@ -678,7 +608,7 @@ class Render extends Admin_Init {
678
  );
679
 
680
  if ( $facebook_page )
681
- return '<meta property="article:author" content="' . \esc_attr( \esc_url_raw( $facebook_page, [ 'http', 'https' ] ) ) . '" />' . "\r\n";
682
 
683
  return '';
684
  }
@@ -714,7 +644,7 @@ class Render extends Admin_Init {
714
  );
715
 
716
  if ( $publisher )
717
- return '<meta property="article:publisher" content="' . \esc_attr( \esc_url_raw( $publisher, [ 'http', 'https' ] ) ) . '" />' . "\r\n";
718
 
719
  return '';
720
  }
@@ -814,7 +744,7 @@ class Render extends Admin_Init {
814
 
815
  $id = $this->get_the_real_ID();
816
 
817
- $post = \get_post( $id );
818
  $post_modified_gmt = $post->post_modified_gmt;
819
 
820
  if ( '0000-00-00 00:00:00' === $post_modified_gmt )
@@ -1031,6 +961,7 @@ class Render extends Admin_Init {
1031
  * Returns early if blog isn't public. WordPress Core will then output the meta tags.
1032
  *
1033
  * @since 2.0.0
 
1034
  *
1035
  * @return string The Robots meta tags.
1036
  */
@@ -1045,7 +976,7 @@ class Render extends Admin_Init {
1045
  if ( empty( $meta ) )
1046
  return '';
1047
 
1048
- return sprintf( '<meta name="robots" content="%s" />' . PHP_EOL, implode( ',', $meta ) );
1049
  }
1050
 
1051
  /**
@@ -1155,63 +1086,62 @@ class Render extends Admin_Init {
1155
  * Returns the plugin hidden HTML indicators.
1156
  *
1157
  * @since 2.9.2
 
 
1158
  *
1159
- * @param string $where Determines the position of the indicator.
1160
- * Accepts 'before' for before, anything else for after.
1161
- * @param int $timing Determines when the output started.
1162
  * @return string The SEO Framework's HTML plugin indicator.
1163
  */
1164
  public function get_plugin_indicator( $where = 'before', $timing = 0 ) {
1165
 
1166
- static $run, $_cache;
1167
 
1168
- if ( ! isset( $run ) ) {
1169
- /**
1170
- * @since 2.0.0
1171
- * @param bool $run Whether to run and show the plugin indicator.
1172
- */
1173
- $run = (bool) \apply_filters( 'the_seo_framework_indicator', true );
 
 
 
 
 
 
 
 
 
 
 
 
 
1174
  }
1175
 
1176
- if ( false === $run )
1177
  return '';
1178
 
1179
- if ( ! isset( $_cache ) ) {
1180
- $_cache = [];
1181
- /**
1182
- * @since 2.4.0
1183
- * @param bool $sybre Whether to show the author name in the indicator.
1184
- */
1185
- $sybre = (bool) \apply_filters( 'sybre_waaijer_<3', true );
1186
-
1187
- // Plugin name can't be translated. Yay.
1188
- $tsf = 'The SEO Framework';
1189
-
1190
- /**
1191
- * @since 2.4.0
1192
- * @param bool $show_timer Whether to show the generation time in the indicator.
1193
- */
1194
- $_cache['show_timer'] = (bool) \apply_filters( 'the_seo_framework_indicator_timing', true );
1195
-
1196
- /* translators: %s = 'The SEO Framework' */
1197
- $_cache['start'] = sprintf( \esc_html__( 'Start %s', 'autodescription' ), $tsf );
1198
- /* translators: %s = 'The SEO Framework' */
1199
- $_cache['end'] = sprintf( \esc_html__( 'End %s', 'autodescription' ), $tsf );
1200
- $_cache['author'] = $sybre ? ' ' . \esc_html__( 'by Sybre Waaijer', 'autodescription' ) : '';
1201
- }
1202
-
1203
  if ( 'before' === $where ) {
1204
- $output = $_cache['start'] . $_cache['author'];
 
 
 
1205
  } else {
1206
- if ( $_cache['show_timer'] && $timing ) {
1207
- $timer = ' | ' . number_format( microtime( true ) - $timing, 5 ) . 's';
 
 
 
 
1208
  } else {
1209
- $timer = '';
1210
  }
1211
- $output = $_cache['end'] . $_cache['author'] . $timer;
1212
- }
1213
 
1214
- return sprintf( '<!-- %s -->', $output ) . PHP_EOL;
 
1215
  }
1216
 
1217
  /**
@@ -1241,7 +1171,7 @@ class Render extends Admin_Init {
1241
  public function output_published_time() {
1242
 
1243
  if ( 'article' !== $this->get_og_type() )
1244
- return $cache = false;
1245
 
1246
  return (bool) $this->get_option( 'post_publish_time' );
1247
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Render
4
+ * @subpackage The_SEO_Framework\Front
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
47
  * @return string The document title
48
  */
49
  public function get_document_title( $title = '' ) {
50
+
51
+ if ( ! $this->query_supports_seo() )
52
  return $title;
53
+
54
  /**
55
  * @since 3.1.0
56
  * @param string $title The generated title.
72
  * Use the_seo_framework()->get_title() instead.
73
  *
74
  * @since 3.1.0
75
+ * @since 4.0.0 Removed extraneous, unused parameters.
76
  * @see $this->get_title()
77
  *
78
+ * @param string $title The filterable title.
79
  * @return string $title
80
  */
81
+ public function get_wp_title( $title = '' ) {
82
+
83
+ if ( ! $this->query_supports_seo() )
84
  return $title;
85
+
86
  /**
87
  * @since 3.1.0
88
  * @param string $title The generated title.
99
 
100
  /**
101
  * Caches current Image URL in static variable.
102
+ * To be used on the front-end only.
103
  *
104
  * @since 2.2.2
105
  * @since 2.7.0 $get_id parameter has been added.
106
+ * @since 4.0.0 Now uses the new image generator.
107
  * @staticvar string $cache
108
  *
109
  * @return string The image URL.
110
  */
111
  public function get_image_from_cache() {
112
 
113
+ $url = '';
114
+
115
+ foreach ( $this->get_image_details_from_cache() as $image ) {
116
+ $url = $image['url'];
117
+ if ( $url ) break;
118
+ }
119
 
120
+ return $url;
121
  }
122
 
123
  /**
299
  */
300
  public function og_image() {
301
 
302
+ if ( ! $this->use_og_tags() ) return '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
 
304
  $output = '';
305
 
306
+ $multi = (bool) $this->get_option( 'multi_og_image' );
 
 
 
 
307
 
308
+ foreach ( $this->get_image_details_from_cache() as $image ) {
309
+ $output .= '<meta property="og:image" content="' . \esc_attr( $image['url'] ) . '" />' . "\r\n";
310
 
311
+ if ( $image['height'] && $image['width'] ) {
312
+ $output .= '<meta property="og:image:width" content="' . \esc_attr( $image['width'] ) . '" />' . "\r\n";
313
+ $output .= '<meta property="og:image:height" content="' . \esc_attr( $image['height'] ) . '" />' . "\r\n";
 
 
 
 
 
 
 
 
314
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
 
316
+ if ( $image['alt'] ) {
317
+ $output .= '<meta property="og:image:alt" content="' . \esc_attr( $image['alt'] ) . '" />' . "\r\n";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  }
319
+
320
+ if ( ! $multi )
321
+ break;
322
  }
323
 
324
  return $output;
334
  */
335
  public function og_sitename() {
336
 
337
+ if ( ! $this->use_og_tags() ) return '';
 
338
 
339
  /**
340
  * @since 2.3.0
553
  */
554
  public function twitter_image() {
555
 
556
+ if ( ! $this->use_twitter_tags() ) return '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
 
558
  $output = '';
559
 
560
+ foreach ( $this->get_image_details_from_cache() as $image ) {
561
+ $output .= '<meta name="twitter:image" content="' . \esc_attr( $image['url'] ) . '" />' . "\r\n";
562
+
563
+ if ( $image['height'] && $image['width'] ) {
564
+ $output .= '<meta name="twitter:image:width" content="' . \esc_attr( $image['width'] ) . '" />' . "\r\n";
565
+ $output .= '<meta name="twitter:image:height" content="' . \esc_attr( $image['height'] ) . '" />' . "\r\n";
566
+ }
567
 
568
+ if ( $image['alt'] ) {
569
+ $output .= '<meta name="twitter:image:alt" content="' . \esc_attr( $image['alt'] ) . '" />' . "\r\n";
 
570
  }
571
+
572
+ // Only grab a single image. Twitter grabs the final (less favorable) image otherwise.
573
+ break;
574
  }
575
 
576
  return $output;
608
  );
609
 
610
  if ( $facebook_page )
611
+ return '<meta property="article:author" content="' . \esc_attr( \esc_url_raw( $facebook_page, [ 'https', 'http' ] ) ) . '" />' . "\r\n";
612
 
613
  return '';
614
  }
644
  );
645
 
646
  if ( $publisher )
647
+ return '<meta property="article:publisher" content="' . \esc_attr( \esc_url_raw( $publisher, [ 'https', 'http' ] ) ) . '" />' . "\r\n";
648
 
649
  return '';
650
  }
744
 
745
  $id = $this->get_the_real_ID();
746
 
747
+ $post = \get_post( $id );
748
  $post_modified_gmt = $post->post_modified_gmt;
749
 
750
  if ( '0000-00-00 00:00:00' === $post_modified_gmt )
961
  * Returns early if blog isn't public. WordPress Core will then output the meta tags.
962
  *
963
  * @since 2.0.0
964
+ * @since 4.0.2 Thanks to special tags, output escaping has been added precautionarily.
965
  *
966
  * @return string The Robots meta tags.
967
  */
976
  if ( empty( $meta ) )
977
  return '';
978
 
979
+ return sprintf( '<meta name="robots" content="%s" />' . PHP_EOL, \esc_attr( implode( ',', $meta ) ) );
980
  }
981
 
982
  /**
1086
  * Returns the plugin hidden HTML indicators.
1087
  *
1088
  * @since 2.9.2
1089
+ * @since 4.0.0 Added boot timers.
1090
+ * @staticvar array $cache
1091
  *
1092
+ * @param string $where Determines the position of the indicator.
1093
+ * Accepts 'before' for before, anything else for after.
1094
+ * @param int $timing Determines when the output started.
1095
  * @return string The SEO Framework's HTML plugin indicator.
1096
  */
1097
  public function get_plugin_indicator( $where = 'before', $timing = 0 ) {
1098
 
1099
+ static $cache;
1100
 
1101
+ if ( ! $cache ) {
1102
+ $cache = [
1103
+ /**
1104
+ * @since 2.0.0
1105
+ * @param bool $run Whether to run and show the plugin indicator.
1106
+ */
1107
+ 'run' => (bool) \apply_filters( 'the_seo_framework_indicator', true ),
1108
+ /**
1109
+ * @since 2.4.0
1110
+ * @param bool $sybre Whether to show the author name in the indicator.
1111
+ */
1112
+ // phpcs:ignore, WordPress.NamingConventions.ValidHookName -- Easter egg.
1113
+ 'author' => (bool) \apply_filters( 'sybre_waaijer_<3', true ) ? \esc_html__( 'by Sybre Waaijer', 'autodescription' ) : '',
1114
+ /**
1115
+ * @since 2.4.0
1116
+ * @param bool $show_timer Whether to show the generation time in the indicator.
1117
+ */
1118
+ 'show_timer' => (bool) \apply_filters( 'the_seo_framework_indicator_timing', true ),
1119
+ ];
1120
  }
1121
 
1122
+ if ( false === $cache['run'] )
1123
  return '';
1124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1125
  if ( 'before' === $where ) {
1126
+ /* translators: 1 = The SEO Framework, 2 = 'by Sybre Waaijer */
1127
+ $output = sprintf( '%1$s %2$s', 'The SEO Framework', $cache['author'] );
1128
+
1129
+ return sprintf( '<!-- %s -->', trim( $output ) ) . PHP_EOL;
1130
  } else {
1131
+ if ( $cache['show_timer'] && $timing ) {
1132
+ $timers = sprintf(
1133
+ ' | %s meta | %s boot',
1134
+ number_format( ( microtime( true ) - $timing ) * 1e3, 2 ) . 'ms',
1135
+ number_format( _bootstrap_timer() * 1e3, 2 ) . 'ms'
1136
+ );
1137
  } else {
1138
+ $timers = '';
1139
  }
1140
+ /* translators: 1 = The SEO Framework, 2 = 'by Sybre Waaijer */
1141
+ $output = sprintf( '%1$s %2$s', 'The SEO Framework', $cache['author'] ) . $timers;
1142
 
1143
+ return sprintf( '<!-- / %s -->', trim( $output ) ) . PHP_EOL;
1144
+ }
1145
  }
1146
 
1147
  /**
1171
  public function output_published_time() {
1172
 
1173
  if ( 'article' !== $this->get_og_type() )
1174
+ return false;
1175
 
1176
  return (bool) $this->get_option( 'post_publish_time' );
1177
  }
inc/classes/sanitize.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -40,6 +42,7 @@ class Sanitize extends Admin_Pages {
40
  *
41
  * @since 2.7.0
42
  * @since 3.1.0 Removed settings field existence check.
 
43
  * @securitycheck 3.0.0 OK.
44
  * @staticvar bool $verified.
45
  *
@@ -59,12 +62,17 @@ class Sanitize extends Admin_Pages {
59
  *
60
  * @since 2.2.9
61
  */
62
- if ( empty( $_POST[ $this->settings_field ] ) // Input var ok.
63
- || ! is_array( $_POST[ $this->settings_field ] ) ) // Input var, CSRF ok: This is just a performance check.
 
 
 
 
64
  return $validated = false;
65
 
66
- //* This is also handled in /wp-admin/options.php. Nevertheless, one might register outside of scope.
67
- \check_admin_referer( $this->settings_field . '-options' );
 
68
 
69
  return $validated = true;
70
  }
@@ -80,9 +88,8 @@ class Sanitize extends Admin_Pages {
80
  */
81
  public function handle_update_post() {
82
 
83
- //* Verify update nonce.
84
- if ( false === $this->verify_seo_settings_nonce() )
85
- return;
86
 
87
  //* Initialize sanitation filters parsed on each option update.
88
  $this->init_sanitizer_filters();
@@ -90,12 +97,26 @@ class Sanitize extends Admin_Pages {
90
  //* Delete main cache now. For when the options don't change.
91
  $this->delete_main_cache();
92
 
 
 
 
 
 
93
  //* Flush transients after options have changed.
94
- \add_action( "update_option_{$this->settings_field}", [ $this, 'delete_main_cache' ] );
95
- \add_action( "update_option_{$this->settings_field}", [ $this, 'reinitialize_rewrite' ], 11 );
96
- \add_action( "update_option_{$this->settings_field}", [ $this, 'update_db_version' ], 12 );
97
- //* TEMP: Set backward compatibility
98
- \add_action( "update_option_{$this->settings_field}", [ $this, '_set_backward_compatibility' ], 13 );
 
 
 
 
 
 
 
 
 
99
  }
100
 
101
  /**
@@ -111,51 +132,35 @@ class Sanitize extends Admin_Pages {
111
  }
112
 
113
  /**
114
- * Maintains backward compatibility for the options of < 3.1.
115
  *
116
  * @since 3.1.0
117
- * @TODO 3.3.0 Remove or empty this.
118
  * @access private
119
- * @staticvar bool $running Prevents loops.
120
  */
121
  public function _set_backward_compatibility() {
122
  static $running = false;
123
  if ( $running ) return;
124
  $running = true;
125
-
126
- db_3101:
127
- //= title_seperator backward compat.
128
- $this->update_option( 'title_seperator', $this->get_option( 'title_separator', false ) );
129
-
130
- //= Media robots backward compat.
131
- foreach ( [ 'noindex', 'nofollow', 'noarchive' ] as $r ) :
132
- $_option = $this->get_option( $this->get_robots_post_type_option_id( $r ), false );
133
- $_media_option = ! empty( $_option['attachment'] ) ? $_option['attachment'] : 0;
134
-
135
- $this->update_option( "attachment_$r", $_media_option );
136
- endforeach;
137
-
138
  end:;
139
-
140
  $running = false;
141
  }
142
 
143
  /**
144
- * Register each of the settings with a sanitization filter type.
145
  *
146
  * @since 2.8.0
147
  * @since 3.1.0 Added caching, preventing duplicate registrations.
148
- * @staticvar bool $init
149
  * @uses $this->add_option_filter() Assign filter to array of settings.
150
  */
151
  public function init_sanitizer_filters() {
152
 
153
- static $init = false;
154
- if ( $init ) return;
155
 
156
  $this->add_option_filter(
157
  's_title_separator',
158
- $this->settings_field,
159
  [
160
  'title_separator',
161
  ]
@@ -163,13 +168,13 @@ class Sanitize extends Admin_Pages {
163
 
164
  $this->add_option_filter(
165
  's_description',
166
- $this->settings_field,
167
  []
168
  );
169
 
170
  $this->add_option_filter(
171
  's_description_raw',
172
- $this->settings_field,
173
  [
174
  'homepage_description',
175
  'homepage_og_description',
@@ -179,7 +184,7 @@ class Sanitize extends Admin_Pages {
179
 
180
  $this->add_option_filter(
181
  's_title',
182
- $this->settings_field,
183
  [
184
  'knowledge_name',
185
  ]
@@ -187,7 +192,7 @@ class Sanitize extends Admin_Pages {
187
 
188
  $this->add_option_filter(
189
  's_title_raw',
190
- $this->settings_field,
191
  [
192
  'homepage_title',
193
  'homepage_title_tagline',
@@ -198,7 +203,7 @@ class Sanitize extends Admin_Pages {
198
 
199
  $this->add_option_filter(
200
  's_knowledge_type',
201
- $this->settings_field,
202
  [
203
  'knowledge_type',
204
  ]
@@ -206,7 +211,7 @@ class Sanitize extends Admin_Pages {
206
 
207
  $this->add_option_filter(
208
  's_left_right',
209
- $this->settings_field,
210
  [
211
  'title_location',
212
  ]
@@ -214,7 +219,7 @@ class Sanitize extends Admin_Pages {
214
 
215
  $this->add_option_filter(
216
  's_left_right_home',
217
- $this->settings_field,
218
  [
219
  'home_title_location',
220
  ]
@@ -222,7 +227,7 @@ class Sanitize extends Admin_Pages {
222
 
223
  $this->add_option_filter(
224
  's_alter_query_type',
225
- $this->settings_field,
226
  [
227
  'alter_archive_query_type',
228
  'alter_search_query_type',
@@ -231,7 +236,7 @@ class Sanitize extends Admin_Pages {
231
 
232
  $this->add_option_filter(
233
  's_one_zero',
234
- $this->settings_field,
235
  [
236
  'alter_search_query',
237
  'alter_archive_query',
@@ -245,6 +250,7 @@ class Sanitize extends Admin_Pages {
245
 
246
  'display_seo_bar_tables',
247
  'display_seo_bar_metabox',
 
248
 
249
  'title_rem_additions',
250
  'title_rem_prefixes',
@@ -257,7 +263,6 @@ class Sanitize extends Admin_Pages {
257
  'author_noindex',
258
  'date_noindex',
259
  'search_noindex',
260
- 'attachment_noindex',
261
  'site_noindex',
262
 
263
  'category_nofollow',
@@ -265,7 +270,6 @@ class Sanitize extends Admin_Pages {
265
  'author_nofollow',
266
  'date_nofollow',
267
  'search_nofollow',
268
- 'attachment_nofollow',
269
  'site_nofollow',
270
 
271
  'category_noarchive',
@@ -273,12 +277,13 @@ class Sanitize extends Admin_Pages {
273
  'author_noarchive',
274
  'date_noarchive',
275
  'search_noarchive',
276
- 'attachment_noarchive',
277
  'site_noarchive',
278
 
279
  'paged_noindex',
280
  'home_paged_noindex',
281
 
 
 
282
  'homepage_noindex',
283
  'homepage_nofollow',
284
  'homepage_noarchive',
@@ -295,6 +300,8 @@ class Sanitize extends Admin_Pages {
295
  'facebook_tags',
296
  'twitter_tags',
297
 
 
 
298
  'knowledge_output',
299
 
300
  'post_publish_time',
@@ -302,6 +309,7 @@ class Sanitize extends Admin_Pages {
302
 
303
  'knowledge_logo',
304
 
 
305
  'ping_google',
306
  'ping_bing',
307
 
@@ -322,7 +330,7 @@ class Sanitize extends Admin_Pages {
322
 
323
  $this->add_option_filter(
324
  's_absint',
325
- $this->settings_field,
326
  [
327
  'social_image_fb_id',
328
  'homepage_social_image_id',
@@ -332,7 +340,7 @@ class Sanitize extends Admin_Pages {
332
 
333
  $this->add_option_filter(
334
  's_numeric_string',
335
- $this->settings_field,
336
  [
337
  'timestamps_format',
338
  ]
@@ -340,7 +348,7 @@ class Sanitize extends Admin_Pages {
340
 
341
  $this->add_option_filter(
342
  's_disabled_post_types',
343
- $this->settings_field,
344
  [
345
  'disabled_post_types',
346
  ]
@@ -348,7 +356,7 @@ class Sanitize extends Admin_Pages {
348
 
349
  $this->add_option_filter(
350
  's_post_types',
351
- $this->settings_field,
352
  [
353
  $this->get_robots_post_type_option_id( 'noindex' ),
354
  $this->get_robots_post_type_option_id( 'nofollow' ),
@@ -356,21 +364,13 @@ class Sanitize extends Admin_Pages {
356
  ]
357
  );
358
 
359
- /*
360
- $this->add_option_filter(
361
- 's_no_html',
362
- $this->settings_field,
363
- []
364
- );
365
- */
366
-
367
  /**
368
  * @todo create content="code" stripper
369
  * @priority low 2.9.0+
370
  */
371
  $this->add_option_filter(
372
  's_no_html_space',
373
- $this->settings_field,
374
  [
375
  'facebook_appid',
376
 
@@ -383,14 +383,13 @@ class Sanitize extends Admin_Pages {
383
 
384
  $this->add_option_filter(
385
  's_url',
386
- $this->settings_field,
387
  [
388
  'knowledge_facebook',
389
  'knowledge_twitter',
390
  'knowledge_gplus',
391
  'knowledge_instagram',
392
  'knowledge_youtube',
393
- // 'knowledge_myspace',
394
  'knowledge_pinterest',
395
  'knowledge_soundcloud',
396
  'knowledge_tumblr',
@@ -399,7 +398,7 @@ class Sanitize extends Admin_Pages {
399
 
400
  $this->add_option_filter(
401
  's_url_query',
402
- $this->settings_field,
403
  [
404
  'knowledge_linkedin',
405
  'social_image_fb_url',
@@ -410,7 +409,7 @@ class Sanitize extends Admin_Pages {
410
 
411
  $this->add_option_filter(
412
  's_facebook_profile',
413
- $this->settings_field,
414
  [
415
  'facebook_publisher',
416
  'facebook_author',
@@ -419,7 +418,7 @@ class Sanitize extends Admin_Pages {
419
 
420
  $this->add_option_filter(
421
  's_twitter_name',
422
- $this->settings_field,
423
  [
424
  'twitter_site',
425
  'twitter_creator',
@@ -428,7 +427,7 @@ class Sanitize extends Admin_Pages {
428
 
429
  $this->add_option_filter(
430
  's_twitter_card',
431
- $this->settings_field,
432
  [
433
  'twitter_card',
434
  ]
@@ -436,7 +435,7 @@ class Sanitize extends Admin_Pages {
436
 
437
  $this->add_option_filter(
438
  's_canonical_scheme',
439
- $this->settings_field,
440
  [
441
  'canonical_scheme',
442
  ]
@@ -444,7 +443,7 @@ class Sanitize extends Admin_Pages {
444
 
445
  $this->add_option_filter(
446
  's_color_hex',
447
- $this->settings_field,
448
  [
449
  'sitemap_color_main',
450
  'sitemap_color_accent',
@@ -453,13 +452,28 @@ class Sanitize extends Admin_Pages {
453
 
454
  $this->add_option_filter(
455
  's_min_max_sitemap',
456
- $this->settings_field,
457
  [
458
  'sitemap_query_limit',
459
  ]
460
  );
461
 
462
- $init = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  }
464
 
465
  /**
@@ -472,17 +486,24 @@ class Sanitize extends Admin_Pages {
472
  * @since 2.2.2
473
  * @since 2.7.0: Uses external caching function.
474
  * @since 2.8.0 Renamed.
 
 
475
  *
476
- * @param string $filter Sanitization filter type
477
- * @param string $option Option key
478
  * @param array|string $suboption Optional. Suboption key(s).
479
  * @return boolean Returns true when complete
480
  */
481
  public function add_option_filter( $filter, $option, $suboption = null ) {
482
 
 
 
483
  $this->set_option_filter( $filter, $option, $suboption );
484
 
485
- \add_filter( 'sanitize_option_' . $option, [ $this, 'sanitize' ], 10, 2 );
 
 
 
486
 
487
  return true;
488
  }
@@ -497,10 +518,10 @@ class Sanitize extends Admin_Pages {
497
  * @since 2.7.0
498
  * @staticvar $options The options filter cache.
499
  *
500
- * @param string $filter Sanitization filter type
501
- * @param string $option Option key
502
  * @param array|string $suboption Optional. Suboption key(s).
503
- * @param bool $get Whether to retrieve cache.
504
  * @return array When $get is true, it will return the option filters.
505
  */
506
  protected function set_option_filter( $filter, $option, $suboption = null, $get = false ) {
@@ -539,8 +560,8 @@ class Sanitize extends Admin_Pages {
539
  *
540
  * @thanks StudioPress (http://www.studiopress.com/) for some code.
541
  *
542
- * @param mixed $new_value New value
543
- * @param string $option Name of the option
544
  * @return mixed Filtered, or unfiltered value
545
  */
546
  public function sanitize( $new_value, $option ) {
@@ -609,32 +630,159 @@ class Sanitize extends Admin_Pages {
609
  * @param array $default_filters Array with keys of sanitization types
610
  * and values of the filter function name as a callback
611
  */
612
- return (array) \apply_filters( 'the_seo_framework_available_sanitizer_filters', [
613
- 's_left_right' => [ $this, 's_left_right' ],
614
- 's_left_right_home' => [ $this, 's_left_right_home' ],
615
- 's_title_separator' => [ $this, 's_title_separator' ],
616
- 's_description' => [ $this, 's_description' ],
617
- 's_description_raw' => [ $this, 's_description_raw' ],
618
- 's_title' => [ $this, 's_title' ],
619
- 's_title_raw' => [ $this, 's_title_raw' ],
620
- 's_knowledge_type' => [ $this, 's_knowledge_type' ],
621
- 's_alter_query_type' => [ $this, 's_alter_query_type' ],
622
- 's_one_zero' => [ $this, 's_one_zero' ],
623
- 's_disabled_post_types' => [ $this, 's_disabled_post_types' ],
624
- 's_post_types' => [ $this, 's_post_types' ],
625
- 's_numeric_string' => [ $this, 's_numeric_string' ],
626
- 's_no_html' => [ $this, 's_no_html' ],
627
- 's_no_html_space' => [ $this, 's_no_html_space' ],
628
- 's_absint' => [ $this, 's_absint' ],
629
- 's_safe_html' => [ $this, 's_safe_html' ],
630
- 's_url' => [ $this, 's_url' ],
631
- 's_url_query' => [ $this, 's_url_query' ],
632
- 's_facebook_profile' => [ $this, 's_facebook_profile' ],
633
- 's_twitter_name' => [ $this, 's_twitter_name' ],
634
- 's_twitter_card' => [ $this, 's_twitter_card' ],
635
- 's_canonical_scheme' => [ $this, 's_canonical_scheme' ],
636
- 's_min_max_sitemap' => [ $this, 's_min_max_sitemap' ],
637
- ] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
  }
639
 
640
  /**
@@ -704,8 +852,9 @@ class Sanitize extends Admin_Pages {
704
  }
705
 
706
  /**
707
- * Returns an one-line sanitized description without nbsp and tabs.
708
  * Does NOT escape.
 
709
  *
710
  * @since 2.8.2
711
  *
@@ -770,6 +919,7 @@ class Sanitize extends Admin_Pages {
770
  * @since 2.8.0
771
  * @since 2.8.2 : 1. Added $allow_shortcodes parameter.
772
  * 2. Added $escape parameter.
 
773
  *
774
  * @param string $excerpt The excerpt.
775
  * @param bool $allow_shortcodes Whether to maintain shortcodes from excerpt.
@@ -856,9 +1006,11 @@ class Sanitize extends Admin_Pages {
856
  }
857
 
858
  /**
859
- * Sanitizes input title as output.
 
860
  *
861
  * @since 2.8.2
 
862
  *
863
  * @param string $new_value The input Title.
864
  * @return string Sanitized, beautified and trimmed title.
@@ -948,16 +1100,11 @@ class Sanitize extends Admin_Pages {
948
  * @return string 'in_query' or 'post_query'
949
  */
950
  public function s_alter_query_type( $new_value ) {
951
- switch ( $new_value ) {
952
- case 'in_query':
953
- case 'post_query':
954
- return (string) $new_value;
955
- break;
956
-
957
- default:
958
- return 'in_query';
959
- break;
960
- }
961
  }
962
 
963
  /**
@@ -975,6 +1122,27 @@ class Sanitize extends Admin_Pages {
975
  return (int) (bool) $new_value;
976
  }
977
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
978
  /**
979
  * Sanitizes disabled post type entries.
980
  *
@@ -982,7 +1150,7 @@ class Sanitize extends Admin_Pages {
982
  *
983
  * @since 3.1.0
984
  *
985
- * @param mixed $new_value Should ideally be an array with post type name indexes, and 1 or 0 passed in.
986
  * @return array
987
  */
988
  public function s_disabled_post_types( $new_values ) {
@@ -1001,7 +1169,7 @@ class Sanitize extends Admin_Pages {
1001
  *
1002
  * @since 3.1.0
1003
  *
1004
- * @param mixed $new_value Should ideally be an array with post type name indexes, and 1 or 0 passed in.
1005
  * @return array
1006
  */
1007
  public function s_post_types( $new_values ) {
@@ -1053,6 +1221,7 @@ class Sanitize extends Admin_Pages {
1053
  * @return string String without HTML in it.
1054
  */
1055
  public function s_no_html( $new_value ) {
 
1056
  return strip_tags( $new_value );
1057
  }
1058
 
@@ -1066,9 +1235,23 @@ class Sanitize extends Admin_Pages {
1066
  * @return string String without HTML and breaks in it.
1067
  */
1068
  public function s_no_html_space( $new_value ) {
 
1069
  return str_replace( ' ', '', strip_tags( $new_value ) );
1070
  }
1071
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1072
  /**
1073
  * Makes URLs safe and removes query args.
1074
  *
@@ -1084,8 +1267,7 @@ class Sanitize extends Admin_Pages {
1084
  * If queries have been tokenized, take the value before the query args.
1085
  * Otherwise it's empty, so take the current value.
1086
  */
1087
- $no_query_url = strtok( $new_value, '?' );
1088
- $url = $no_query_url ?: $new_value;
1089
 
1090
  return \esc_url_raw( $url );
1091
  }
@@ -1103,6 +1285,29 @@ class Sanitize extends Admin_Pages {
1103
  return \esc_url_raw( $new_value );
1104
  }
1105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1106
  /**
1107
  * Makes Email Addresses safe, via sanitize_email()
1108
  *
@@ -1137,18 +1342,26 @@ class Sanitize extends Admin_Pages {
1137
  * @since 2.8.0 Method is now public.
1138
  * @since 3.0.0: 1. Now removes '@' from the URL path.
1139
  * 2. Now removes spaces and tabs.
 
 
1140
  *
1141
  * @param string $new_value String with potentially wrong Twitter username.
1142
  * @return string String with 'correct' Twitter username
1143
  */
1144
  public function s_twitter_name( $new_value ) {
1145
 
1146
- if ( empty( $new_value ) )
1147
- return '';
 
 
 
 
 
 
1148
 
1149
- $profile = trim( strip_tags( $new_value ) );
1150
- $profile = $this->s_relative_url( $profile );
1151
- $profile = rtrim( $profile, ' /' );
1152
 
1153
  if ( '@' !== substr( $profile, 0, 1 ) )
1154
  $profile = '@' . $profile;
@@ -1162,19 +1375,28 @@ class Sanitize extends Admin_Pages {
1162
  * @since 2.2.2
1163
  * @since 2.8.0 Method is now public.
1164
  * @since 3.0.6 Now allows a sole query argument when profile.php is used.
 
 
1165
  *
1166
  * @param string $new_value String with potentially wrong Facebook profile URL.
1167
  * @return string String with 'correct' Facebook profile URL.
1168
  */
1169
  public function s_facebook_profile( $new_value ) {
1170
 
1171
- if ( empty( $new_value ) )
1172
- return '';
 
 
 
 
 
 
 
 
1173
 
1174
- $link = trim( strip_tags( $new_value ) );
1175
 
1176
- $link = 'https://www.facebook.com/' . $this->s_relative_url( $link );
1177
- $link = rtrim( $link, ' /' );
1178
 
1179
  if ( strpos( $link, 'profile.php' ) ) {
1180
  //= Gets query parameters.
@@ -1210,8 +1432,7 @@ class Sanitize extends Admin_Pages {
1210
 
1211
  $key = array_key_exists( $new_value, $card );
1212
 
1213
- if ( $key )
1214
- return (string) $new_value;
1215
 
1216
  $previous = $this->get_option( 'twitter_card' );
1217
 
@@ -1222,82 +1443,48 @@ class Sanitize extends Admin_Pages {
1222
  }
1223
 
1224
  /**
1225
- * Converts full URL paths to absolute paths.
1226
- *
1227
- * Removes the http or https protocols and the domain. Keeps the path '/' at the
1228
- * beginning, so it isn't a true relative link, but from the web root base.
1229
  *
1230
  * @since 2.6.5
1231
  * @since 2.8.0 Method is now public.
 
1232
  *
1233
  * @param string $url Full Path URL or relative URL.
1234
  * @return string Abolute path.
1235
  */
1236
  public function s_relative_url( $url ) {
1237
- return ltrim( preg_replace( '|^(https?:)?//[^/]+(/.*)|i', '$2', $url ), ' \//' );
1238
  }
1239
 
1240
  /**
1241
- * Sanitizes the Redirect URL
1242
  *
1243
  * @since 2.2.4
1244
  * @since 2.8.0 Method is now public.
1245
  * @since 3.0.6 Noqueries is now disabled by default.
 
 
 
 
 
1246
  *
1247
  * @param string $new_value String with potentially unwanted redirect URL.
1248
  * @return string The Sanitized Redirect URL
1249
  */
1250
  public function s_redirect_url( $new_value ) {
1251
 
 
1252
  $url = strip_tags( $new_value );
1253
 
1254
- if ( $url ) :
1255
- /**
1256
- * Sanitize the redirect URL to only a relative link and removes first slash
1257
- * @requires WP 4.1.0 and up to prevent adding upon itself.
1258
- */
1259
- if ( ! $this->allow_external_redirect() )
1260
- $url = $this->s_relative_url( $url );
1261
-
1262
- //* URL pattern excluding path.
1263
- $pattern = '/'
1264
- . '((((http)(s)?)?)\:)?' // 1: maybe http: https:
1265
- . '(\/\/)?' // 2: maybe slash slash
1266
- . '((www.)?)' // 3: maybe www.
1267
- . '(.*\.[a-zA-Z0-9]*)' // 4: any legal domain with tld
1268
- . '(?:\/)?' // 5: maybe trailing slash
1269
- . '/'; // precision alignment OK.
1270
-
1271
- //* If link is relative, make it full again
1272
- if ( ! preg_match( $pattern, $url ) ) {
1273
-
1274
- //* The url is a relative path
1275
- $path = $url;
1276
-
1277
- /**
1278
- * Filters arguments for sanitation of the redirection URL.
1279
- *
1280
- * @since 2.8.0
1281
- *
1282
- * @param array : { 'url' => The full URL built from $path, 'scheme' => The preferred scheme }
1283
- * @param string $path the URL path.
1284
- */
1285
- $custom_sanitize = (array) \apply_filters( 'the_seo_framework_sanitize_redirect_args', [], $path );
1286
-
1287
- if ( $custom_sanitize ) {
1288
- $url = $custom_sanitize['url'];
1289
- $scheme = $custom_sanitize['scheme'];
1290
- } else {
1291
- $url = \trailingslashit( $this->get_homepage_permalink() ) . ltrim( $path, ' /' );
1292
- $scheme = $this->is_ssl() ? 'https' : 'http';
1293
- }
1294
 
1295
- //* When nothing is found, fall back on WP defaults (is_ssl).
1296
- $scheme = isset( $scheme ) ? $scheme : '';
 
1297
 
1298
- $url = $this->set_url_scheme( $url, $scheme );
1299
- }
1300
- endif;
1301
 
1302
  /**
1303
  * @since 2.5.0
@@ -1327,7 +1514,6 @@ class Sanitize extends Admin_Pages {
1327
  $new_value = $this->s_url_query( $url );
1328
  }
1329
 
1330
- //* Save url
1331
  return $new_value;
1332
  }
1333
 
@@ -1346,7 +1532,7 @@ class Sanitize extends Admin_Pages {
1346
  if ( '' === $color )
1347
  return '';
1348
 
1349
- if ( preg_match( '|^([A-Fa-f0-9]{3}){1,2}$|', $color ) )
1350
  return $color;
1351
 
1352
  return '';
@@ -1377,6 +1563,18 @@ class Sanitize extends Admin_Pages {
1377
  return str_replace( '\\', '&#92;', stripslashes( $new_value ) );
1378
  }
1379
 
 
 
 
 
 
 
 
 
 
 
 
 
1380
  /**
1381
  * Sanitizes canonical scheme settings.
1382
  *
@@ -1423,27 +1621,64 @@ class Sanitize extends Admin_Pages {
1423
  return $new_value;
1424
  }
1425
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1426
  /**
1427
  * Sanitizeses ID. Mainly removing spaces and coding characters.
1428
  *
1429
  * Unlike sanitize_key(), it doesn't alter the case nor applies filters.
1430
- * It also maintains the '@' character.
1431
  *
1432
  * @see WordPress Core sanitize_key()
1433
- * @since 3.1.0
 
1434
  *
1435
  * @param string $id The unsanitized ID.
1436
  * @return string The sanitized ID.
1437
  */
1438
- public function sanitize_field_id( $id ) {
1439
- return preg_replace( '/[^a-zA-Z0-9_\-@]/', '', $id );
1440
  }
1441
 
1442
  /**
1443
  * Strips all URLs that are placed on new lines. These are prone to be embeds.
1444
  *
1445
- * This might leave stray line feeds.
1446
- * @see $this->s_singleline();
1447
  *
1448
  * @since 3.1.0
1449
  * @see \WP_Embed::autoembed()
@@ -1458,8 +1693,7 @@ class Sanitize extends Admin_Pages {
1458
  /**
1459
  * Strips all URLs that are placed in paragraphs on their own. These are prone to be embeds.
1460
  *
1461
- * This might leave stray line feeds.
1462
- * @see $this->s_singleline();
1463
  *
1464
  * @since 3.1.0
1465
  * @see \WP_Embed::autoembed()
@@ -1480,19 +1714,22 @@ class Sanitize extends Admin_Pages {
1480
  * Tip: You might want to use method `s_dupe_space()` to clear up the duplicated spaces afterward.
1481
  *
1482
  * @since 3.2.4
 
1483
  * @link: https://www.w3schools.com/html/html_blocks.asp
1484
  *
1485
  * @param string $input The input text that needs its tags stripped.
1486
  * @param array $args The input arguments: {
1487
- * 'space' : @param array|null HTML elements that should have a space added.
1488
  * If not set or null, skip check.
1489
- * If empty array, use default; otherwise, use array.
1490
  * 'clear' : @param array|null HTML elements that should be emptied and replaced with a space.
1491
  * If not set or null, skip check.
1492
- * If empty array, use default; otherwise, use array.
1493
  * }
1494
  * NOTE: WARNING The array values are forwarded to a regex without sanitization.
1495
  * NOTE: Unlisted, script, and style tags will be stripped via PHP's `strip_tags()`.
 
 
1496
  * @return string The output string without tags.
1497
  */
1498
  public function strip_tags_cs( $input, $args = [] ) {
@@ -1510,7 +1747,7 @@ class Sanitize extends Admin_Pages {
1510
  foreach ( [ 'space', 'clear' ] as $type ) {
1511
  if ( isset( $args[ $type ] ) ) {
1512
  if ( ! $args[ $type ] ) {
1513
- $args[ $type ] = $default_args[ $type ];
1514
  } else {
1515
  $args[ $type ] = (array) $args[ $type ];
1516
  }
@@ -1528,6 +1765,105 @@ class Sanitize extends Admin_Pages {
1528
  $input = preg_replace( "/$_regex/si", $_replace, $input );
1529
  }
1530
 
 
1531
  return strip_tags( $input );
1532
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1533
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Sanitize
4
+ * @subpackage The_SEO_Framework\Admin
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
42
  *
43
  * @since 2.7.0
44
  * @since 3.1.0 Removed settings field existence check.
45
+ * @since 4.0.0 Added redundant user capability check.
46
  * @securitycheck 3.0.0 OK.
47
  * @staticvar bool $verified.
48
  *
62
  *
63
  * @since 2.2.9
64
  */
65
+ if ( empty( $_POST[ THE_SEO_FRAMEWORK_SITE_OPTIONS ] )
66
+ || ! is_array( $_POST[ THE_SEO_FRAMEWORK_SITE_OPTIONS ] ) )
67
+ return $validated = false;
68
+
69
+ // This is also handled in /wp-admin/options.php. Nevertheless, one might register outside of scope.
70
+ if ( ! \current_user_can( $this->get_settings_capability() ) )
71
  return $validated = false;
72
 
73
+ // This is also handled in /wp-admin/options.php. Nevertheless, one might register outside of scope.
74
+ // This also checks the nonce: `_wpnonce`.
75
+ \check_admin_referer( THE_SEO_FRAMEWORK_SITE_OPTIONS . '-options' );
76
 
77
  return $validated = true;
78
  }
88
  */
89
  public function handle_update_post() {
90
 
91
+ //* Verify update headers.
92
+ if ( ! $this->verify_seo_settings_nonce() ) return;
 
93
 
94
  //* Initialize sanitation filters parsed on each option update.
95
  $this->init_sanitizer_filters();
97
  //* Delete main cache now. For when the options don't change.
98
  $this->delete_main_cache();
99
 
100
+ //* Sets that the options are unchanged, preemptively.
101
+ $this->update_static_cache( 'settings_notice', 'unchanged' );
102
+ //* But, if this action fires, we can assure that the settings have been changed.
103
+ \add_action( 'update_option_' . THE_SEO_FRAMEWORK_SITE_OPTIONS, [ $this, '_set_option_updated_notice' ], 0 );
104
+
105
  //* Flush transients after options have changed.
106
+ \add_action( 'update_option_' . THE_SEO_FRAMEWORK_SITE_OPTIONS, [ $this, 'delete_main_cache' ] );
107
+ \add_action( 'update_option_' . THE_SEO_FRAMEWORK_SITE_OPTIONS, [ $this, 'update_db_version' ], 12 );
108
+ //* TEMP: Set backward compatibility.
109
+ // \add_action( 'update_option_' . THE_SEO_FRAMEWORK_SITE_OPTIONS, [ $this, '_set_backward_compatibility' ], 13 );
110
+ }
111
+
112
+ /**
113
+ * Sets the settings notice cache to "updated".
114
+ *
115
+ * @since 4.0.0
116
+ * @access private
117
+ */
118
+ public function _set_option_updated_notice() {
119
+ $this->update_static_cache( 'settings_notice', 'updated' );
120
  }
121
 
122
  /**
132
  }
133
 
134
  /**
135
+ * Maintains backward compatibility for older, migrated options.
136
  *
137
  * @since 3.1.0
138
+ * @since 4.0.0 Emptied and is no longer enqueued.
139
  * @access private
140
+ * @staticvar bool $running Prevents on-update loops.
141
  */
142
  public function _set_backward_compatibility() {
143
  static $running = false;
144
  if ( $running ) return;
145
  $running = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  end:;
 
147
  $running = false;
148
  }
149
 
150
  /**
151
+ * Registers each of the settings with a sanitization filter type.
152
  *
153
  * @since 2.8.0
154
  * @since 3.1.0 Added caching, preventing duplicate registrations.
 
155
  * @uses $this->add_option_filter() Assign filter to array of settings.
156
  */
157
  public function init_sanitizer_filters() {
158
 
159
+ if ( _has_run( __METHOD__ ) ) return;
 
160
 
161
  $this->add_option_filter(
162
  's_title_separator',
163
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
164
  [
165
  'title_separator',
166
  ]
168
 
169
  $this->add_option_filter(
170
  's_description',
171
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
172
  []
173
  );
174
 
175
  $this->add_option_filter(
176
  's_description_raw',
177
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
178
  [
179
  'homepage_description',
180
  'homepage_og_description',
184
 
185
  $this->add_option_filter(
186
  's_title',
187
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
188
  [
189
  'knowledge_name',
190
  ]
192
 
193
  $this->add_option_filter(
194
  's_title_raw',
195
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
196
  [
197
  'homepage_title',
198
  'homepage_title_tagline',
203
 
204
  $this->add_option_filter(
205
  's_knowledge_type',
206
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
207
  [
208
  'knowledge_type',
209
  ]
211
 
212
  $this->add_option_filter(
213
  's_left_right',
214
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
215
  [
216
  'title_location',
217
  ]
219
 
220
  $this->add_option_filter(
221
  's_left_right_home',
222
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
223
  [
224
  'home_title_location',
225
  ]
227
 
228
  $this->add_option_filter(
229
  's_alter_query_type',
230
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
231
  [
232
  'alter_archive_query_type',
233
  'alter_search_query_type',
236
 
237
  $this->add_option_filter(
238
  's_one_zero',
239
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
240
  [
241
  'alter_search_query',
242
  'alter_archive_query',
250
 
251
  'display_seo_bar_tables',
252
  'display_seo_bar_metabox',
253
+ 'seo_bar_symbols',
254
 
255
  'title_rem_additions',
256
  'title_rem_prefixes',
263
  'author_noindex',
264
  'date_noindex',
265
  'search_noindex',
 
266
  'site_noindex',
267
 
268
  'category_nofollow',
270
  'author_nofollow',
271
  'date_nofollow',
272
  'search_nofollow',
 
273
  'site_nofollow',
274
 
275
  'category_noarchive',
277
  'author_noarchive',
278
  'date_noarchive',
279
  'search_noarchive',
 
280
  'site_noarchive',
281
 
282
  'paged_noindex',
283
  'home_paged_noindex',
284
 
285
+ 'set_copyright_directives',
286
+
287
  'homepage_noindex',
288
  'homepage_nofollow',
289
  'homepage_noarchive',
300
  'facebook_tags',
301
  'twitter_tags',
302
 
303
+ 'multi_og_image',
304
+
305
  'knowledge_output',
306
 
307
  'post_publish_time',
309
 
310
  'knowledge_logo',
311
 
312
+ 'ping_use_cron',
313
  'ping_google',
314
  'ping_bing',
315
 
330
 
331
  $this->add_option_filter(
332
  's_absint',
333
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
334
  [
335
  'social_image_fb_id',
336
  'homepage_social_image_id',
340
 
341
  $this->add_option_filter(
342
  's_numeric_string',
343
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
344
  [
345
  'timestamps_format',
346
  ]
348
 
349
  $this->add_option_filter(
350
  's_disabled_post_types',
351
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
352
  [
353
  'disabled_post_types',
354
  ]
356
 
357
  $this->add_option_filter(
358
  's_post_types',
359
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
360
  [
361
  $this->get_robots_post_type_option_id( 'noindex' ),
362
  $this->get_robots_post_type_option_id( 'nofollow' ),
364
  ]
365
  );
366
 
 
 
 
 
 
 
 
 
367
  /**
368
  * @todo create content="code" stripper
369
  * @priority low 2.9.0+
370
  */
371
  $this->add_option_filter(
372
  's_no_html_space',
373
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
374
  [
375
  'facebook_appid',
376
 
383
 
384
  $this->add_option_filter(
385
  's_url',
386
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
387
  [
388
  'knowledge_facebook',
389
  'knowledge_twitter',
390
  'knowledge_gplus',
391
  'knowledge_instagram',
392
  'knowledge_youtube',
 
393
  'knowledge_pinterest',
394
  'knowledge_soundcloud',
395
  'knowledge_tumblr',
398
 
399
  $this->add_option_filter(
400
  's_url_query',
401
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
402
  [
403
  'knowledge_linkedin',
404
  'social_image_fb_url',
409
 
410
  $this->add_option_filter(
411
  's_facebook_profile',
412
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
413
  [
414
  'facebook_publisher',
415
  'facebook_author',
418
 
419
  $this->add_option_filter(
420
  's_twitter_name',
421
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
422
  [
423
  'twitter_site',
424
  'twitter_creator',
427
 
428
  $this->add_option_filter(
429
  's_twitter_card',
430
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
431
  [
432
  'twitter_card',
433
  ]
435
 
436
  $this->add_option_filter(
437
  's_canonical_scheme',
438
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
439
  [
440
  'canonical_scheme',
441
  ]
443
 
444
  $this->add_option_filter(
445
  's_color_hex',
446
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
447
  [
448
  'sitemap_color_main',
449
  'sitemap_color_accent',
452
 
453
  $this->add_option_filter(
454
  's_min_max_sitemap',
455
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
456
  [
457
  'sitemap_query_limit',
458
  ]
459
  );
460
 
461
+ $this->add_option_filter(
462
+ 's_image_preview',
463
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
464
+ [
465
+ 'max_image_preview',
466
+ ]
467
+ );
468
+
469
+ $this->add_option_filter(
470
+ 's_snippet_length',
471
+ THE_SEO_FRAMEWORK_SITE_OPTIONS,
472
+ [
473
+ 'max_snippet_length',
474
+ 'max_video_preview',
475
+ ]
476
+ );
477
  }
478
 
479
  /**
486
  * @since 2.2.2
487
  * @since 2.7.0: Uses external caching function.
488
  * @since 2.8.0 Renamed.
489
+ * @since 4.0.0 Now caches its $option registration.
490
+ * @staticvar array $cache
491
  *
492
+ * @param string $filter Sanitization filter type
493
+ * @param string $option The option key.
494
  * @param array|string $suboption Optional. Suboption key(s).
495
  * @return boolean Returns true when complete
496
  */
497
  public function add_option_filter( $filter, $option, $suboption = null ) {
498
 
499
+ static $cache = [];
500
+
501
  $this->set_option_filter( $filter, $option, $suboption );
502
 
503
+ if ( ! isset( $cache[ $option ] ) ) {
504
+ \add_filter( 'sanitize_option_' . $option, [ $this, 'sanitize' ], 10, 2 );
505
+ $cache[ $option ] = true;
506
+ }
507
 
508
  return true;
509
  }
518
  * @since 2.7.0
519
  * @staticvar $options The options filter cache.
520
  *
521
+ * @param string $filter Sanitization filter type
522
+ * @param string $option Option key
523
  * @param array|string $suboption Optional. Suboption key(s).
524
+ * @param bool $get Whether to retrieve cache.
525
  * @return array When $get is true, it will return the option filters.
526
  */
527
  protected function set_option_filter( $filter, $option, $suboption = null, $get = false ) {
560
  *
561
  * @thanks StudioPress (http://www.studiopress.com/) for some code.
562
  *
563
+ * @param mixed $new_value New value
564
+ * @param string $option Name of the option
565
  * @return mixed Filtered, or unfiltered value
566
  */
567
  public function sanitize( $new_value, $option ) {
630
  * @param array $default_filters Array with keys of sanitization types
631
  * and values of the filter function name as a callback
632
  */
633
+ return (array) \apply_filters(
634
+ 'the_seo_framework_available_sanitizer_filters',
635
+ [
636
+ 's_left_right' => [ $this, 's_left_right' ],
637
+ 's_left_right_home' => [ $this, 's_left_right_home' ],
638
+ 's_title_separator' => [ $this, 's_title_separator' ],
639
+ 's_description' => [ $this, 's_description' ],
640
+ 's_description_raw' => [ $this, 's_description_raw' ],
641
+ 's_title' => [ $this, 's_title' ],
642
+ 's_title_raw' => [ $this, 's_title_raw' ],
643
+ 's_knowledge_type' => [ $this, 's_knowledge_type' ],
644
+ 's_alter_query_type' => [ $this, 's_alter_query_type' ],
645
+ 's_one_zero' => [ $this, 's_one_zero' ],
646
+ 's_disabled_post_types' => [ $this, 's_disabled_post_types' ],
647
+ 's_post_types' => [ $this, 's_post_types' ],
648
+ 's_numeric_string' => [ $this, 's_numeric_string' ],
649
+ 's_no_html' => [ $this, 's_no_html' ],
650
+ 's_no_html_space' => [ $this, 's_no_html_space' ],
651
+ 's_absint' => [ $this, 's_absint' ],
652
+ 's_safe_html' => [ $this, 's_safe_html' ],
653
+ 's_url' => [ $this, 's_url' ],
654
+ 's_url_query' => [ $this, 's_url_query' ],
655
+ 's_facebook_profile' => [ $this, 's_facebook_profile' ],
656
+ 's_twitter_name' => [ $this, 's_twitter_name' ],
657
+ 's_twitter_card' => [ $this, 's_twitter_card' ],
658
+ 's_canonical_scheme' => [ $this, 's_canonical_scheme' ],
659
+ 's_min_max_sitemap' => [ $this, 's_min_max_sitemap' ],
660
+ 's_image_preview' => [ $this, 's_image_preview' ],
661
+ 's_snippet_length' => [ $this, 's_snippet_length' ],
662
+ ]
663
+ );
664
+ }
665
+
666
+ /**
667
+ * Sanitizes term meta.
668
+ *
669
+ * @since 4.0.0
670
+ *
671
+ * @param array $data The term meta to sanitize.
672
+ * @return array The sanitized term meta.
673
+ */
674
+ public function s_term_meta( array $data ) {
675
+
676
+ foreach ( $data as $key => &$value ) :
677
+ switch ( $key ) :
678
+ case 'doctitle':
679
+ case 'og_title':
680
+ case 'tw_title':
681
+ $value = $this->s_title_raw( $value );
682
+ continue 2;
683
+
684
+ case 'description':
685
+ case 'og_description':
686
+ case 'tw_description':
687
+ $value = $this->s_description_raw( $value );
688
+ continue 2;
689
+
690
+ case 'canonical':
691
+ case 'social_image_url':
692
+ $value = $this->s_url_query( $value );
693
+ continue 2;
694
+
695
+ case 'social_image_id':
696
+ //* Bound to social_image_url.
697
+ $value = $data['social_image_url'] ? $this->s_absint( $value ) : 0;
698
+ continue 2;
699
+
700
+ case 'noindex':
701
+ case 'nofollow':
702
+ case 'noarchive':
703
+ $value = $this->s_qubit( $value );
704
+ continue 2;
705
+
706
+ case 'redirect':
707
+ $value = $this->s_redirect_url( $value );
708
+ continue 2;
709
+
710
+ case 'title_no_blog_name':
711
+ $value = $this->s_one_zero( $value );
712
+ continue 2;
713
+
714
+ default:
715
+ unset( $data[ $key ] );
716
+ break;
717
+ endswitch;
718
+ endforeach;
719
+
720
+ return $data;
721
+ }
722
+
723
+ /**
724
+ * Sanitizes post meta.
725
+ *
726
+ * @since 4.0.0
727
+ *
728
+ * @param array $data The post meta to sanitize.
729
+ * @return array The sanitized post meta.
730
+ */
731
+ public function s_post_meta( array $data ) {
732
+
733
+ foreach ( $data as $key => &$value ) :
734
+ switch ( $key ) :
735
+ case '_genesis_title':
736
+ case '_open_graph_title':
737
+ case '_twitter_title':
738
+ $value = $this->s_title_raw( $value );
739
+ continue 2;
740
+
741
+ case '_genesis_description':
742
+ case '_open_graph_description':
743
+ case '_twitter_description':
744
+ $value = $this->s_description_raw( $value );
745
+ continue 2;
746
+
747
+ case '_genesis_canonical_uri':
748
+ case '_social_image_url':
749
+ /**
750
+ * Remove unwanted query parameters. They're allowed by Google, but very much rather not.
751
+ * Also, they will only cause bugs.
752
+ * Query parameters are also only used when no pretty permalinks are used. Which is bad.
753
+ */
754
+ $value = $this->s_url_query( $value );
755
+ continue 2;
756
+
757
+ case '_social_image_id':
758
+ //* Bound to _social_image_url.
759
+ $value = $data['_social_image_url'] ? $this->s_absint( $value ) : 0;
760
+ continue 2;
761
+
762
+ case '_genesis_noindex':
763
+ case '_genesis_nofollow':
764
+ case '_genesis_noarchive':
765
+ $value = $this->s_qubit( $value );
766
+ continue 2;
767
+
768
+ case 'redirect':
769
+ //* Let's keep this as the output really is.
770
+ $value = $this->s_redirect_url( $value );
771
+ continue 2;
772
+
773
+ case '_tsf_title_no_blogname':
774
+ case 'exclude_local_search':
775
+ case 'exclude_from_archive':
776
+ $value = $this->s_one_zero( $value );
777
+ continue 2;
778
+
779
+ default:
780
+ unset( $data[ $key ] );
781
+ break;
782
+ endswitch;
783
+ endforeach;
784
+
785
+ return $data;
786
  }
787
 
788
  /**
852
  }
853
 
854
  /**
855
+ * Returns an single-line, trimmed description without dupliacated spaces, nbsp, or tabs.
856
  * Does NOT escape.
857
+ * Also converts back-solidi to their respective HTML entities for non-destructive handling.
858
  *
859
  * @since 2.8.2
860
  *
919
  * @since 2.8.0
920
  * @since 2.8.2 : 1. Added $allow_shortcodes parameter.
921
  * 2. Added $escape parameter.
922
+ * @see `$this->strip_tags_cs()`
923
  *
924
  * @param string $excerpt The excerpt.
925
  * @param bool $allow_shortcodes Whether to maintain shortcodes from excerpt.
1006
  }
1007
 
1008
  /**
1009
+ * Returns an single-line, trimmed title without dupliacated spaces, nbsp, or tabs.
1010
+ * Also converts back-solidi to their respective HTML entities for non-destructive handling.
1011
  *
1012
  * @since 2.8.2
1013
+ * @since 4.0.0 Now normalizes `&` entities.
1014
  *
1015
  * @param string $new_value The input Title.
1016
  * @return string Sanitized, beautified and trimmed title.
1100
  * @return string 'in_query' or 'post_query'
1101
  */
1102
  public function s_alter_query_type( $new_value ) {
1103
+
1104
+ if ( in_array( $new_value, [ 'in_query', 'post_query' ], true ) )
1105
+ return $new_value;
1106
+
1107
+ return 'in_query';
 
 
 
 
 
1108
  }
1109
 
1110
  /**
1122
  return (int) (bool) $new_value;
1123
  }
1124
 
1125
+ /**
1126
+ * Returns a -1, 0, or 1, based on nearest value.
1127
+ *
1128
+ * @since 4.0.0
1129
+ *
1130
+ * @param mixed $new_value Should ideally be -1, 0, or 1.
1131
+ * @return int -1, 0, or 1.
1132
+ */
1133
+ public function s_qubit( $new_value ) {
1134
+
1135
+ if ( $new_value < -.33 ) {
1136
+ $new_value = -1;
1137
+ } elseif ( $new_value > .33 ) {
1138
+ $new_value = 1;
1139
+ } else {
1140
+ $new_value = 0;
1141
+ }
1142
+
1143
+ return $new_value;
1144
+ }
1145
+
1146
  /**
1147
  * Sanitizes disabled post type entries.
1148
  *
1150
  *
1151
  * @since 3.1.0
1152
  *
1153
+ * @param mixed $new_values Should ideally be an array with post type name indexes, and 1 or 0 passed in.
1154
  * @return array
1155
  */
1156
  public function s_disabled_post_types( $new_values ) {
1169
  *
1170
  * @since 3.1.0
1171
  *
1172
+ * @param mixed $new_values Should ideally be an array with post type name indexes, and 1 or 0 passed in.
1173
  * @return array
1174
  */
1175
  public function s_post_types( $new_values ) {
1221
  * @return string String without HTML in it.
1222
  */
1223
  public function s_no_html( $new_value ) {
1224
+ // phpcs:ignore, WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- This is simple and performant sanity.
1225
  return strip_tags( $new_value );
1226
  }
1227
 
1235
  * @return string String without HTML and breaks in it.
1236
  */
1237
  public function s_no_html_space( $new_value ) {
1238
+ // phpcs:ignore, WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- This is simple and performant sanity.
1239
  return str_replace( ' ', '', strip_tags( $new_value ) );
1240
  }
1241
 
1242
+ /**
1243
+ * Escapes attributes after converting `&` to `&amp;` to prevent double-escaping
1244
+ * of entities in HTML input value attributes.
1245
+ *
1246
+ * @since 4.0.0
1247
+ *
1248
+ * @param string $new_value String with possibly ampersands.
1249
+ * @return string
1250
+ */
1251
+ public function esc_attr_preserve_amp( $new_value ) {
1252
+ return \esc_attr( str_replace( '&', '&amp;', $new_value ) );
1253
+ }
1254
+
1255
  /**
1256
  * Makes URLs safe and removes query args.
1257
  *
1267
  * If queries have been tokenized, take the value before the query args.
1268
  * Otherwise it's empty, so take the current value.
1269
  */
1270
+ $url = strtok( $new_value, '?' ) ?: $new_value;
 
1271
 
1272
  return \esc_url_raw( $url );
1273
  }
1285
  return \esc_url_raw( $new_value );
1286
  }
1287
 
1288
+ /**
1289
+ * Makes non-relative URLs absolute, corrects the scheme to most preferred when the
1290
+ * domain matches the current site, and makes it safer regardless afterward.
1291
+ *
1292
+ * Could not think of a good name. Enjoy.
1293
+ *
1294
+ * @since 4.0.2
1295
+ *
1296
+ * @param string $new_value String, an (invalid) URL, possibly unsafe.
1297
+ * @return string String a safe URL with Query Arguments.
1298
+ */
1299
+ public function s_url_relative_to_current_scheme( $new_value ) {
1300
+
1301
+ if ( $this->matches_this_domain( $new_value ) ) {
1302
+ $url = $this->set_preferred_url_scheme( $new_value );
1303
+ } else {
1304
+ // This also sets preferred URL scheme if path.
1305
+ $url = $this->convert_to_url_if_path( $new_value );
1306
+ }
1307
+
1308
+ return $this->s_url_query( $url );
1309
+ }
1310
+
1311
  /**
1312
  * Makes Email Addresses safe, via sanitize_email()
1313
  *
1342
  * @since 2.8.0 Method is now public.
1343
  * @since 3.0.0: 1. Now removes '@' from the URL path.
1344
  * 2. Now removes spaces and tabs.
1345
+ * @since 4.0.0: 1. Now returns empty on lone `@` entries.
1346
+ * 2. Now returns empty when using only spaces and tabs.
1347
  *
1348
  * @param string $new_value String with potentially wrong Twitter username.
1349
  * @return string String with 'correct' Twitter username
1350
  */
1351
  public function s_twitter_name( $new_value ) {
1352
 
1353
+ // phpcs:ignore, WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- This is simple and performant sanity.
1354
+ $new_value = strip_tags( $new_value );
1355
+ $new_value = $this->s_singleline( $new_value );
1356
+ $new_value = $this->s_nbsp( $new_value );
1357
+ $new_value = $this->s_tabs( $new_value );
1358
+ $new_value = trim( $new_value );
1359
+
1360
+ if ( empty( $new_value ) ) return '';
1361
 
1362
+ $profile = trim( $this->s_relative_url( $new_value ), ' /' );
1363
+
1364
+ if ( '@' === $profile ) return '';
1365
 
1366
  if ( '@' !== substr( $profile, 0, 1 ) )
1367
  $profile = '@' . $profile;
1375
  * @since 2.2.2
1376
  * @since 2.8.0 Method is now public.
1377
  * @since 3.0.6 Now allows a sole query argument when profile.php is used.
1378
+ * @since 4.0.0: 1. No longer returns a plain Facebook URL when the entry path is sanitized to become empty.
1379
+ * 2. Now returns empty when using only spaces and tabs.
1380
  *
1381
  * @param string $new_value String with potentially wrong Facebook profile URL.
1382
  * @return string String with 'correct' Facebook profile URL.
1383
  */
1384
  public function s_facebook_profile( $new_value ) {
1385
 
1386
+ // phpcs:ignore, WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- This is simple and performant sanity.
1387
+ $new_value = strip_tags( $new_value );
1388
+ $new_value = $this->s_singleline( $new_value );
1389
+ $new_value = $this->s_nbsp( $new_value );
1390
+ $new_value = $this->s_tabs( $new_value );
1391
+ $new_value = trim( $new_value );
1392
+
1393
+ if ( empty( $new_value ) ) return '';
1394
+
1395
+ $path = trim( $this->s_relative_url( $new_value ), ' /' );
1396
 
1397
+ if ( ! $path ) return '';
1398
 
1399
+ $link = 'https://www.facebook.com/' . $path;
 
1400
 
1401
  if ( strpos( $link, 'profile.php' ) ) {
1402
  //= Gets query parameters.
1432
 
1433
  $key = array_key_exists( $new_value, $card );
1434
 
1435
+ if ( $key ) return (string) $new_value;
 
1436
 
1437
  $previous = $this->get_option( 'twitter_card' );
1438
 
1443
  }
1444
 
1445
  /**
1446
+ * Converts absolute URLs to relative URLs, if they weren't already.
1447
+ * The method should more aptly be named: "maybe_make_url_relative()".
 
 
1448
  *
1449
  * @since 2.6.5
1450
  * @since 2.8.0 Method is now public.
1451
+ * @since 4.0.0 No longer strips the prepended / path.
1452
  *
1453
  * @param string $url Full Path URL or relative URL.
1454
  * @return string Abolute path.
1455
  */
1456
  public function s_relative_url( $url ) {
1457
+ return preg_replace( '/^(https?:)?\/\/[^\/]+(\/.*)/i', '$2', $url );
1458
  }
1459
 
1460
  /**
1461
+ * Sanitizes the Redirect URL.
1462
  *
1463
  * @since 2.2.4
1464
  * @since 2.8.0 Method is now public.
1465
  * @since 3.0.6 Noqueries is now disabled by default.
1466
+ * @since 4.0.0 : 1. Removed rudimentary relative URL testing.
1467
+ * 2. Removed input transformation filters, and with that, removed redundant multisite spam protection.
1468
+ * 3. Now allows all protocols. Enjoy!
1469
+ * 4. Now no longer lets through double-absolute URLs (e.g. `https://google.com/https://google.com/path/to/file/`)
1470
+ * when filter `the_seo_framework_allow_external_redirect` is set to false.
1471
  *
1472
  * @param string $new_value String with potentially unwanted redirect URL.
1473
  * @return string The Sanitized Redirect URL
1474
  */
1475
  public function s_redirect_url( $new_value ) {
1476
 
1477
+ // phpcs:ignore, WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- This is simple, performant sanity.
1478
  $url = strip_tags( $new_value );
1479
 
1480
+ if ( ! $url ) return '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1481
 
1482
+ // This is also checked when performing a redirect.
1483
+ if ( ! $this->allow_external_redirect() )
1484
+ $url = $this->set_url_scheme( $url, 'relative' );
1485
 
1486
+ // Only adjust scheme if it used to be relative. Do not use `s_url_relative_to_current_scheme()`.
1487
+ $url = $this->convert_to_url_if_path( $url );
 
1488
 
1489
  /**
1490
  * @since 2.5.0
1514
  $new_value = $this->s_url_query( $url );
1515
  }
1516
 
 
1517
  return $new_value;
1518
  }
1519
 
1532
  if ( '' === $color )
1533
  return '';
1534
 
1535
+ if ( preg_match( '/^([A-Fa-f0-9]{3}){1,2}$/', $color ) )
1536
  return $color;
1537
 
1538
  return '';
1563
  return str_replace( '\\', '&#92;', stripslashes( $new_value ) );
1564
  }
1565
 
1566
+ /**
1567
+ * Replaces backslash with entity backslash.
1568
+ *
1569
+ * @since 4.0.0
1570
+ *
1571
+ * @param string $new_value String with potentially wanted \ values.
1572
+ * @return string A string with safe HTML encoded backslashes.
1573
+ */
1574
+ public function s_bsol_raw( $new_value ) {
1575
+ return str_replace( '\\', '&#92;', $new_value );
1576
+ }
1577
+
1578
  /**
1579
  * Sanitizes canonical scheme settings.
1580
  *
1621
  return $new_value;
1622
  }
1623
 
1624
+ /**
1625
+ * Sanitizes image preview directive value.
1626
+ *
1627
+ * @since 4.0.2
1628
+ *
1629
+ * @param string $new_value String with potentially unwanted values.
1630
+ * @return string The robots image snippet preview directive value.
1631
+ */
1632
+ public function s_image_preview( $new_value ) {
1633
+
1634
+ if ( ! in_array( $new_value, [ 'none', 'standard', 'large' ], true ) )
1635
+ $new_value = 'standard';
1636
+
1637
+ return $new_value;
1638
+ }
1639
+
1640
+ /**
1641
+ * Sanitizes video and snippet preview length directive values.
1642
+ *
1643
+ * @since 4.0.2
1644
+ *
1645
+ * @param int $new_value Integer with potentially unwanted values.
1646
+ * @return int The robots video and snippet preview directive value.
1647
+ */
1648
+ public function s_snippet_length( $new_value ) {
1649
+
1650
+ $new_value = (int) $new_value;
1651
+
1652
+ if ( $new_value < 0 ) {
1653
+ $new_value = -1;
1654
+ } elseif ( $new_value > 600 ) {
1655
+ $new_value = 600;
1656
+ }
1657
+
1658
+ return $new_value;
1659
+ }
1660
+
1661
  /**
1662
  * Sanitizeses ID. Mainly removing spaces and coding characters.
1663
  *
1664
  * Unlike sanitize_key(), it doesn't alter the case nor applies filters.
1665
+ * It also maintains the '@' character and square brackets.
1666
  *
1667
  * @see WordPress Core sanitize_key()
1668
+ * @since 4.0.0
1669
+ * @deprecated
1670
  *
1671
  * @param string $id The unsanitized ID.
1672
  * @return string The sanitized ID.
1673
  */
1674
+ public function s_field_id( $id ) {
1675
+ return preg_replace( '/[^a-zA-Z0-9\[\]_\-@]/', '', $id );
1676
  }
1677
 
1678
  /**
1679
  * Strips all URLs that are placed on new lines. These are prone to be embeds.
1680
  *
1681
+ * This might leave stray line feeds. Use `the_seo_framework()->s_singleline()` to fix that.
 
1682
  *
1683
  * @since 3.1.0
1684
  * @see \WP_Embed::autoembed()
1693
  /**
1694
  * Strips all URLs that are placed in paragraphs on their own. These are prone to be embeds.
1695
  *
1696
+ * This might leave stray line feeds. Use `the_seo_framework()->s_singleline()` to fix that.
 
1697
  *
1698
  * @since 3.1.0
1699
  * @see \WP_Embed::autoembed()
1714
  * Tip: You might want to use method `s_dupe_space()` to clear up the duplicated spaces afterward.
1715
  *
1716
  * @since 3.2.4
1717
+ * @since 4.0.0 Now allows emptying the indexes `space` and `clear`.
1718
  * @link: https://www.w3schools.com/html/html_blocks.asp
1719
  *
1720
  * @param string $input The input text that needs its tags stripped.
1721
  * @param array $args The input arguments: {
1722
+ * 'space' : @param array|null HTML elements that should have a space added around it.
1723
  * If not set or null, skip check.
1724
+ * If empty array, skips stripping; otherwise, use input.
1725
  * 'clear' : @param array|null HTML elements that should be emptied and replaced with a space.
1726
  * If not set or null, skip check.
1727
+ * If empty array, skips stripping; otherwise, use input.
1728
  * }
1729
  * NOTE: WARNING The array values are forwarded to a regex without sanitization.
1730
  * NOTE: Unlisted, script, and style tags will be stripped via PHP's `strip_tags()`.
1731
+ * Also note that their contents are maintained as-is, without added spaces.
1732
+ * It is why you should always list `style` and `script` in the `clear` array.
1733
  * @return string The output string without tags.
1734
  */
1735
  public function strip_tags_cs( $input, $args = [] ) {
1747
  foreach ( [ 'space', 'clear' ] as $type ) {
1748
  if ( isset( $args[ $type ] ) ) {
1749
  if ( ! $args[ $type ] ) {
1750
+ $args[ $type ] = [];
1751
  } else {
1752
  $args[ $type ] = (array) $args[ $type ];
1753
  }
1765
  $input = preg_replace( "/$_regex/si", $_replace, $input );
1766
  }
1767
 
1768
+ // phpcs:ignore, WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- $args defines stripping of 'script' and 'style'.
1769
  return strip_tags( $input );
1770
  }
1771
+
1772
+ /**
1773
+ * Cleans known parameters from image details.
1774
+ *
1775
+ * @since 4.0.0
1776
+ * @since 4.0.2 Now finds smaller images when they're over 4K.
1777
+ * @NOTE If the input details are in an associative array, they'll be converted to sequential.
1778
+ *
1779
+ * @param array $details The image details, either associative (see $defaults) or sequential.
1780
+ * @return array The image details array, sequential: int => {
1781
+ * string url: The image URL,
1782
+ * int id: The image ID,
1783
+ * int width: The image width in pixels,
1784
+ * int height: The image height in pixels,
1785
+ * string alt: The image alt tag,
1786
+ * }
1787
+ */
1788
+ public function s_image_details( array $details ) {
1789
+
1790
+ if ( array_values( $details ) === $details )
1791
+ return $this->s_image_details_deep( $details );
1792
+
1793
+ $defaults = [
1794
+ 'url' => '',
1795
+ 'id' => 0,
1796
+ 'width' => 0,
1797
+ 'height' => 0,
1798
+ 'alt' => '',
1799
+ ];
1800
+
1801
+ list( $url, $id, $width, $height, $alt ) = array_values( array_merge( $defaults, $details ) );
1802
+
1803
+ if ( ! $url ) return $defaults;
1804
+
1805
+ $url = $this->s_url_relative_to_current_scheme( $url );
1806
+
1807
+ if ( ! $url ) return $defaults;
1808
+
1809
+ $width = (int) $width;
1810
+ $height = (int) $height;
1811
+
1812
+ if ( ! $width || ! $height )
1813
+ $width = $height = 0;
1814
+
1815
+ if ( $width > 4096 || $height > 4096 ) {
1816
+ $new_image = $this->get_largest_acceptable_image_src( $id, 4096 );
1817
+ $url = $new_image ? $this->s_url_relative_to_current_scheme( $new_image[0] ) : '';
1818
+
1819
+ if ( ! $url ) return $defaults;
1820
+
1821
+ // No sanitization needed. PHP's getimagesize() returns the correct values.
1822
+ $width = $new_image[1];
1823
+ $height = $new_image[2];
1824
+ }
1825
+
1826
+ if ( $alt ) {
1827
+ $alt = \wp_strip_all_tags( $alt );
1828
+ // 420: https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/summary.html
1829
+ // Don't "ai"-trim if under, it's unlikely to always be a sentence.
1830
+ $alt = strlen( $alt ) > 420 ? $this->trim_excerpt( $alt, 0, 420 ) : $alt;
1831
+ }
1832
+
1833
+ return compact( 'url', 'id', 'width', 'height', 'alt' );
1834
+ }
1835
+
1836
+ /**
1837
+ * Iterates over and cleans known parameters from image details. Also strips out duplicates.
1838
+ *
1839
+ * @since 4.0.0
1840
+ *
1841
+ * @param array $details_array The image details, preferably sequential.
1842
+ * @return array The image details array, sequential: int => {
1843
+ * string url: The image URL,
1844
+ * int id: The image ID,
1845
+ * int width: The image width in pixels,
1846
+ * int height: The image height in pixels,
1847
+ * string alt: The image alt tag,
1848
+ * }
1849
+ */
1850
+ public function s_image_details_deep( array $details_array ) {
1851
+
1852
+ $cleaned_details = [];
1853
+
1854
+ // Failsafe. Convert associative detailts to a multidimensional sequential array.
1855
+ if ( isset( $details_array['url'] ) )
1856
+ $details_array = [ $details_array ];
1857
+
1858
+ foreach ( $details_array as $details ) {
1859
+ $cleaned_details[] = $this->s_image_details( $details );
1860
+ }
1861
+
1862
+ return array_values(
1863
+ array_intersect_key(
1864
+ $cleaned_details,
1865
+ array_unique( array_filter( array_column( $cleaned_details, 'url' ) ) )
1866
+ )
1867
+ );
1868
+ }
1869
  }
inc/classes/silencer.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -44,7 +46,7 @@ final class Silencer {
44
  }
45
 
46
  public function __set( $name, $value ) {
47
- return;
48
  }
49
 
50
  public function __isset( $name ) {
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;
46
  }
47
 
48
  public function __set( $name, $value ) {
49
+ return $value;
50
  }
51
 
52
  public function __isset( $name ) {
inc/classes/site-options.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -32,15 +34,6 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
32
  */
33
  class Site_Options extends Sanitize {
34
 
35
- /**
36
- * Site Settings field.
37
- *
38
- * @since 2.2.2
39
- *
40
- * @var string Settings field.
41
- */
42
- protected $settings_field = THE_SEO_FRAMEWORK_SITE_OPTIONS;
43
-
44
  /**
45
  * Hold the SEO Settings Page ID for this plugin.
46
  *
@@ -56,28 +49,25 @@ class Site_Options extends Sanitize {
56
  *
57
  * @since 2.6.0
58
  * @since 3.1.0 Now applies filters 'the_seo_framework_default_site_options'
 
59
  *
60
  * @return array Default site options.
61
  */
62
  public function get_default_site_options() {
63
 
64
- /**
65
- * Switch when RTL is active;
66
- * @since 2.5.0
67
- */
68
  if ( \is_rtl() ) {
69
  $titleloc = 'left';
70
- $h_titleloc = 'right';
71
  } else {
72
  $titleloc = 'right';
73
- $h_titleloc = 'left';
74
  }
75
 
 
76
  /**
77
  * @since 2.2.7
78
  * @param array $options The default site options.
79
  */
80
- // phpcs:disable WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned -- precision alignment OK.
81
  return (array) \apply_filters(
82
  'the_seo_framework_default_site_options',
83
  [
@@ -95,6 +85,7 @@ class Site_Options extends Sanitize {
95
  // General. Layout.
96
  'display_seo_bar_tables' => 1, // SEO Bar post-list tables.
97
  'display_seo_bar_metabox' => 0, // SEO Bar post SEO Settings.
 
98
 
99
  'display_pixel_counter' => 1, // Pixel counter.
100
  'display_character_counter' => 1, // Character counter.
@@ -124,7 +115,6 @@ class Site_Options extends Sanitize {
124
  'author_noindex' => 0, // Author Archive robots noindex
125
  'date_noindex' => 1, // Date Archive robots noindex
126
  'search_noindex' => 1, // Search Page robots noindex
127
- 'attachment_noindex' => 1, // Attachment Pages robots noindex. NOTE BACKWARD COMPAT.
128
  'site_noindex' => 0, // Site Page robots noindex
129
 
130
  $this->get_robots_post_type_option_id( 'noindex' ) => [
@@ -137,7 +127,6 @@ class Site_Options extends Sanitize {
137
  'author_nofollow' => 0, // Author Archive robots nofollow
138
  'date_nofollow' => 0, // Date Archive robots nofollow
139
  'search_nofollow' => 0, // Search Page robots nofollow
140
- 'attachment_nofollow' => 0, // Attachment Pages robots noindex. NOTE BACKWARD COMPAT.
141
  'site_nofollow' => 0, // Site Page robots nofollow
142
 
143
  $this->get_robots_post_type_option_id( 'nofollow' ) => [], // Post Type support.
@@ -148,7 +137,6 @@ class Site_Options extends Sanitize {
148
  'author_noarchive' => 0, // Author Archive robots noarchive
149
  'date_noarchive' => 0, // Date Archive robots noarchive
150
  'search_noarchive' => 0, // Search Page robots noarchive
151
- 'attachment_noarchive' => 0, // Attachment Page robots noarchive. NOTE BACKWARD COMPAT.
152
  'site_noarchive' => 0, // Site Page robots noarchive
153
 
154
  $this->get_robots_post_type_option_id( 'noarchive' ) => [], // Post Type support.
@@ -157,6 +145,12 @@ class Site_Options extends Sanitize {
157
  'paged_noindex' => 1, // Every second or later page noindex
158
  'home_paged_noindex' => 0, // Every second or later homepage noindex
159
 
 
 
 
 
 
 
160
  // Robots home.
161
  'homepage_noindex' => 0, // Homepage robots noindex
162
  'homepage_nofollow' => 0, // Homepage robots noarchive
@@ -203,6 +197,9 @@ class Site_Options extends Sanitize {
203
  'facebook_tags' => 1, // Output the Facebook meta tags
204
  'twitter_tags' => 1, // Output the Twitter meta tags
205
 
 
 
 
206
  // Social FallBack images (fb = fallback)
207
  'social_image_fb_url' => '', // Fallback image URL
208
  'social_image_fb_id' => 0, // Fallback image ID
@@ -232,20 +229,20 @@ class Site_Options extends Sanitize {
232
  'knowledge_instagram' => '', // Instagram Account
233
  'knowledge_youtube' => '', // Youtube Account
234
  'knowledge_linkedin' => '', // Linkedin Account
235
- // 'knowledge_myspace' => '', // MySpace Account // meh.
236
  'knowledge_pinterest' => '', // Pinterest Account
237
  'knowledge_soundcloud' => '', // SoundCloud Account
238
  'knowledge_tumblr' => '', // Tumblr Account
239
 
240
  // Sitemaps.
241
  'sitemaps_output' => 1, // Output of sitemap
242
- 'sitemap_query_limit' => 1200, // Sitemap post limit.
243
 
244
  'sitemaps_modified' => 1, // Add sitemap modified time.
245
  'sitemaps_priority' => 0, // Add sitemap priorities.
246
 
247
  'sitemaps_robots' => 1, // Add sitemap location to robots.txt
248
 
 
249
  'ping_google' => 1, // Ping Google
250
  'ping_bing' => 1, // Ping Bing
251
 
@@ -263,7 +260,7 @@ class Site_Options extends Sanitize {
263
  'ld_json_breadcrumbs' => 1, // LD+Json Breadcrumbs
264
  ]
265
  );
266
- // phpcs:enable WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned
267
  }
268
 
269
  /**
@@ -306,14 +303,14 @@ class Site_Options extends Sanitize {
306
  * @since 2.2.2
307
  *
308
  * @uses $this->the_seo_framework_get_option() Return option from the options table and cache result.
309
- * @uses $this->settings_field
310
  *
311
  * @param string $key Option name.
312
  * @param boolean $use_cache Optional. Whether to use the cache value or not. Defaults to true.
313
  * @return mixed The value of this $key in the database.
314
  */
315
  public function get_option( $key, $use_cache = true ) {
316
- return $this->the_seo_framework_get_option( $key, $this->settings_field, $use_cache );
317
  }
318
 
319
  /**
@@ -324,8 +321,8 @@ class Site_Options extends Sanitize {
324
  * @staticvar array $cache The option cache.
325
  *
326
  * @param string $setting The setting key.
327
- * @param bool $use_current Whether to use WordPress' version and update the cache
328
- * or use the locally cached version.
329
  * @return array Options.
330
  */
331
  public function get_all_options( $setting = null, $use_current = false ) {
@@ -336,7 +333,7 @@ class Site_Options extends Sanitize {
336
  return $cache[ $setting ];
337
 
338
  if ( ! $setting )
339
- $setting = $this->settings_field;
340
 
341
  /**
342
  * @since 2.0.0
@@ -366,7 +363,7 @@ class Site_Options extends Sanitize {
366
  *
367
  * @param string $key Option name.
368
  * @param string $setting Optional. Settings field name. Eventually defaults to null if not passed as an argument.
369
- * @param boolean $use_cache Optional. Whether to use the cache value or not. Default is true.
370
  * @return mixed The value of this $key in the database. Empty string on failure.
371
  */
372
  public function the_seo_framework_get_option( $key, $setting = null, $use_cache = true ) {
@@ -395,8 +392,8 @@ class Site_Options extends Sanitize {
395
  *
396
  * @todo deprecate, unused.
397
  *
398
- * @param string $key Option name.
399
- * @param boolean $use_cache Optional. Whether to use the cache value or not. Defaults to true.
400
  * @return mixed The value of this $key in the database.
401
  */
402
  public function get_site_option( $key, $use_cache = true ) {
@@ -408,10 +405,10 @@ class Site_Options extends Sanitize {
408
  *
409
  * @since 2.2.5
410
  * @uses $this->get_default_settings() Return option from the options table and cache result.
411
- * @uses $this->settings_field
412
  *
413
- * @param string $key Option name.
414
- * @param boolean $use_cache Optional. Whether to use the cache value or not. Defaults to true.
415
  * @return mixed The value of this $key in the database.
416
  */
417
  public function get_default_option( $key, $use_cache = true ) {
@@ -424,17 +421,23 @@ class Site_Options extends Sanitize {
424
  * @since 2.2.2
425
  * @since 2.9.0 Removed reset options check, see check_options_reset().
426
  * @since 3.1.0 Removed settings field existence check.
 
427
  * @thanks StudioPress (http://www.studiopress.com/) for some code.
428
  *
429
  * @return void Early if settings can't be registered.
430
  */
431
  public function register_settings() {
432
 
433
- \register_setting( $this->settings_field, $this->settings_field );
434
- \add_option( $this->settings_field, $this->get_default_site_options() );
 
435
 
436
  //* Check whether the Options Reset initialization has been added.
437
  $this->check_options_reset();
 
 
 
 
438
  }
439
 
440
  /**
@@ -458,14 +461,14 @@ class Site_Options extends Sanitize {
458
  *
459
  * @since 3.1.0
460
  *
461
- * @param string $key The cache key. Required.
462
  * @param string $value The cache value.
463
  * @return bool True on success, false on failure.
464
  */
465
  public function update_static_cache( $key, $value = '' ) {
466
 
467
  if ( ! $key ) {
468
- $this->_doing_it_wrong( __METHOD__, 'No cache key has been specified.', '3.1.0' );
469
  return false;
470
  }
471
 
@@ -481,34 +484,28 @@ class Site_Options extends Sanitize {
481
  */
482
  protected function check_options_reset() {
483
 
484
- /**
485
- * Security check:
486
- * Further checks are based on previously set options, via option 'tsf-settings-reset'.
487
- * These can only be set when one has access to the Settings Page or database.
488
- * Also checks for capabilities.
489
- */
490
- if ( ! $this->can_access_settings() || ! $this->is_seo_settings_page( false ) )
491
  return;
492
 
493
  if ( $this->get_option( 'tsf-settings-reset', false ) ) {
494
- if ( \update_option( $this->settings_field, $this->get_default_site_options() ) ) {
495
- $this->admin_redirect( $this->seo_settings_page_slug, [ 'tsf-settings-reset' => 'true' ] );
496
- exit;
497
  } else {
498
- $this->admin_redirect( $this->seo_settings_page_slug, [ 'error' => 'true' ] );
499
- exit;
500
  }
 
 
501
  }
502
  }
503
 
504
  /**
505
- * Updates a single option.
506
  *
507
  * Can return false if option is unchanged.
508
  *
509
  * @since 2.9.0
510
  *
511
- * @param string $key The option key.
512
  * @param string $value The option value.
513
  * @return bool True on success, false on failure.
514
  */
@@ -523,11 +520,11 @@ class Site_Options extends Sanitize {
523
  }
524
 
525
  /**
526
- * Allows updating of settings.
527
  *
528
  * @since 2.7.0
529
  *
530
- * @param string|array $new_option {
531
  * if string: The string will act as a key for a new empty string option, e.g. : {
532
  * 'sitemap_index' becomes ['sitemap_index' => '']
533
  * }
@@ -535,18 +532,18 @@ class Site_Options extends Sanitize {
535
  * ['sitemap_index' => 1]
536
  * }
537
  * }
538
- * @param string $settings_field The Settings Field to update. Defaults
539
- * to The SEO Framework settings field.
540
  * @return bool True on success. False on failure.
541
  */
542
- public function update_settings( $new = '', $settings_field = '' ) {
543
 
544
  if ( ! $settings_field ) {
545
- $settings_field = $this->settings_field;
546
  $this->init_sanitizer_filters();
547
  }
548
 
549
- $settings = \wp_parse_args( $new, \get_option( $settings_field ) );
550
 
551
  return \update_option( $settings_field, $settings );
552
  }
@@ -563,9 +560,9 @@ class Site_Options extends Sanitize {
563
  * @staticvar array $defaults_cache
564
  * @uses $this->get_default_site_options()
565
  *
566
- * @param string $key required The option name
567
- * @param string $depr Deprecated. Leave empty.
568
- * @param bool $use_cache optional Use the options cache or not. For debugging purposes.
569
  * @return mixed default option
570
  * null If option doesn't exist.
571
  */
@@ -596,12 +593,12 @@ class Site_Options extends Sanitize {
596
  * @since 2.3.4
597
  * @since 3.1.0 : Now returns 0 if the option doesn't exist, instead of -1.
598
  * @staticvar array $warned_cache
599
- * @uses $this->settings_field
600
  * @uses $this->get_warned_site_options()
601
  *
602
- * @param string $key required The option name
603
- * @param string $depr Deprecated. Leave empty.
604
- * @param bool $use_cache optional Use the options cache or not. For debugging purposes.
605
  * @return int 0|1 Whether the option is flagged as dangerous for SEO.
606
  */
607
  public function get_warned_settings( $key, $depr = '', $use_cache = true ) {
@@ -635,14 +632,16 @@ class Site_Options extends Sanitize {
635
  * @return string
636
  */
637
  public function get_robots_post_type_option_id( $type ) {
638
- return $this->sanitize_field_id( $type . '_post_types' );
639
  }
640
 
641
  /**
642
  * Returns Facebook locales array values.
643
  *
644
  * @since 2.5.2
645
- * @see https://www.facebook.com/translations/FacebookLocales.xml
 
 
646
  * @see $this->language_keys() for the associative array keys.
647
  *
648
  * @return array Valid Facebook locales
@@ -799,7 +798,9 @@ class Site_Options extends Sanitize {
799
  * Use this to compare the numeric key position.
800
  *
801
  * @since 2.5.2
802
- * @see https://www.facebook.com/translations/FacebookLocales.xml
 
 
803
  *
804
  * @return array Valid Facebook locale keys
805
  */
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Site_Options
4
+ * @subpackage The_SEO_Framework\Data
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
34
  */
35
  class Site_Options extends Sanitize {
36
 
 
 
 
 
 
 
 
 
 
37
  /**
38
  * Hold the SEO Settings Page ID for this plugin.
39
  *
49
  *
50
  * @since 2.6.0
51
  * @since 3.1.0 Now applies filters 'the_seo_framework_default_site_options'
52
+ * @since 4.0.0 `home_title_location` is now switched from right to left, or vice-versa.
53
  *
54
  * @return array Default site options.
55
  */
56
  public function get_default_site_options() {
57
 
 
 
 
 
58
  if ( \is_rtl() ) {
59
  $titleloc = 'left';
60
+ $h_titleloc = 'left';
61
  } else {
62
  $titleloc = 'right';
63
+ $h_titleloc = 'right';
64
  }
65
 
66
+ // phpcs:disable, WordPress.Arrays.MultipleStatementAlignment -- precision alignment OK.
67
  /**
68
  * @since 2.2.7
69
  * @param array $options The default site options.
70
  */
 
71
  return (array) \apply_filters(
72
  'the_seo_framework_default_site_options',
73
  [
85
  // General. Layout.
86
  'display_seo_bar_tables' => 1, // SEO Bar post-list tables.
87
  'display_seo_bar_metabox' => 0, // SEO Bar post SEO Settings.
88
+ 'seo_bar_symbols' => 0, // SEO Bar symbolic display settings.
89
 
90
  'display_pixel_counter' => 1, // Pixel counter.
91
  'display_character_counter' => 1, // Character counter.
115
  'author_noindex' => 0, // Author Archive robots noindex
116
  'date_noindex' => 1, // Date Archive robots noindex
117
  'search_noindex' => 1, // Search Page robots noindex
 
118
  'site_noindex' => 0, // Site Page robots noindex
119
 
120
  $this->get_robots_post_type_option_id( 'noindex' ) => [
127
  'author_nofollow' => 0, // Author Archive robots nofollow
128
  'date_nofollow' => 0, // Date Archive robots nofollow
129
  'search_nofollow' => 0, // Search Page robots nofollow
 
130
  'site_nofollow' => 0, // Site Page robots nofollow
131
 
132
  $this->get_robots_post_type_option_id( 'nofollow' ) => [], // Post Type support.
137
  'author_noarchive' => 0, // Author Archive robots noarchive
138
  'date_noarchive' => 0, // Date Archive robots noarchive
139
  'search_noarchive' => 0, // Search Page robots noarchive
 
140
  'site_noarchive' => 0, // Site Page robots noarchive
141
 
142
  $this->get_robots_post_type_option_id( 'noarchive' ) => [], // Post Type support.
145
  'paged_noindex' => 1, // Every second or later page noindex
146
  'home_paged_noindex' => 0, // Every second or later homepage noindex
147
 
148
+ // Robots copyright.
149
+ 'set_copyright_directives' => 1, // Allow copyright directive settings.
150
+ 'max_snippet_length' => -1, // Max text-snippet length. -1 = unlimited, 0 = disabled, R>0 = characters.
151
+ 'max_image_preview' => 'standard', // Max image-preview size. 'none', 'standard', 'large'.
152
+ 'max_video_preview' => -1, // Max video-preview size. -1 = unlimited, 0 = disabled, R>0 = seconds.
153
+
154
  // Robots home.
155
  'homepage_noindex' => 0, // Homepage robots noindex
156
  'homepage_nofollow' => 0, // Homepage robots noarchive
197
  'facebook_tags' => 1, // Output the Facebook meta tags
198
  'twitter_tags' => 1, // Output the Twitter meta tags
199
 
200
+ // Social image settings.
201
+ 'multi_og_image' => 1,
202
+
203
  // Social FallBack images (fb = fallback)
204
  'social_image_fb_url' => '', // Fallback image URL
205
  'social_image_fb_id' => 0, // Fallback image ID
229
  'knowledge_instagram' => '', // Instagram Account
230
  'knowledge_youtube' => '', // Youtube Account
231
  'knowledge_linkedin' => '', // Linkedin Account
 
232
  'knowledge_pinterest' => '', // Pinterest Account
233
  'knowledge_soundcloud' => '', // SoundCloud Account
234
  'knowledge_tumblr' => '', // Tumblr Account
235
 
236
  // Sitemaps.
237
  'sitemaps_output' => 1, // Output of sitemap
238
+ 'sitemap_query_limit' => 3000, // Sitemap post limit.
239
 
240
  'sitemaps_modified' => 1, // Add sitemap modified time.
241
  'sitemaps_priority' => 0, // Add sitemap priorities.
242
 
243
  'sitemaps_robots' => 1, // Add sitemap location to robots.txt
244
 
245
+ 'ping_use_cron' => 1, // Ping using CRON.
246
  'ping_google' => 1, // Ping Google
247
  'ping_bing' => 1, // Ping Bing
248
 
260
  'ld_json_breadcrumbs' => 1, // LD+Json Breadcrumbs
261
  ]
262
  );
263
+ // phpcs:enable, WordPress.Arrays.MultipleStatementAlignment
264
  }
265
 
266
  /**
303
  * @since 2.2.2
304
  *
305
  * @uses $this->the_seo_framework_get_option() Return option from the options table and cache result.
306
+ * @uses THE_SEO_FRAMEWORK_SITE_OPTIONS
307
  *
308
  * @param string $key Option name.
309
  * @param boolean $use_cache Optional. Whether to use the cache value or not. Defaults to true.
310
  * @return mixed The value of this $key in the database.
311
  */
312
  public function get_option( $key, $use_cache = true ) {
313
+ return $this->the_seo_framework_get_option( $key, THE_SEO_FRAMEWORK_SITE_OPTIONS, $use_cache );
314
  }
315
 
316
  /**
321
  * @staticvar array $cache The option cache.
322
  *
323
  * @param string $setting The setting key.
324
+ * @param bool $use_current Whether to use WordPress' version and update the cache
325
+ * or use the locally cached version.
326
  * @return array Options.
327
  */
328
  public function get_all_options( $setting = null, $use_current = false ) {
333
  return $cache[ $setting ];
334
 
335
  if ( ! $setting )
336
+ $setting = THE_SEO_FRAMEWORK_SITE_OPTIONS;
337
 
338
  /**
339
  * @since 2.0.0
363
  *
364
  * @param string $key Option name.
365
  * @param string $setting Optional. Settings field name. Eventually defaults to null if not passed as an argument.
366
+ * @param boolean $use_cache Optional. Whether to use the cache value or not.
367
  * @return mixed The value of this $key in the database. Empty string on failure.
368
  */
369
  public function the_seo_framework_get_option( $key, $setting = null, $use_cache = true ) {
392
  *
393
  * @todo deprecate, unused.
394
  *
395
+ * @param string $key Required. The option name.
396
+ * @param boolean $use_cache Optional. Whether to use the cache value or not.
397
  * @return mixed The value of this $key in the database.
398
  */
399
  public function get_site_option( $key, $use_cache = true ) {
405
  *
406
  * @since 2.2.5
407
  * @uses $this->get_default_settings() Return option from the options table and cache result.
408
+ * @uses THE_SEO_FRAMEWORK_SITE_OPTIONS
409
  *
410
+ * @param string $key Required. The option name.
411
+ * @param boolean $use_cache Optional. Whether to use the cache value or not.
412
  * @return mixed The value of this $key in the database.
413
  */
414
  public function get_default_option( $key, $use_cache = true ) {
421
  * @since 2.2.2
422
  * @since 2.9.0 Removed reset options check, see check_options_reset().
423
  * @since 3.1.0 Removed settings field existence check.
424
+ * @since 4.0.0 Now checks if the option exists before adding it. Shaves 20μs...
425
  * @thanks StudioPress (http://www.studiopress.com/) for some code.
426
  *
427
  * @return void Early if settings can't be registered.
428
  */
429
  public function register_settings() {
430
 
431
+ \register_setting( THE_SEO_FRAMEWORK_SITE_OPTIONS, THE_SEO_FRAMEWORK_SITE_OPTIONS );
432
+ \get_option( THE_SEO_FRAMEWORK_SITE_OPTIONS )
433
+ or \add_option( THE_SEO_FRAMEWORK_SITE_OPTIONS, $this->get_default_site_options() );
434
 
435
  //* Check whether the Options Reset initialization has been added.
436
  $this->check_options_reset();
437
+
438
+ //* Handle post-update actions. Must be initialized on admin_init and is initalized on options.php.
439
+ if ( 'options.php' === $GLOBALS['pagenow'] )
440
+ $this->handle_update_post();
441
  }
442
 
443
  /**
461
  *
462
  * @since 3.1.0
463
  *
464
+ * @param string $key The cache key. Required.
465
  * @param string $value The cache value.
466
  * @return bool True on success, false on failure.
467
  */
468
  public function update_static_cache( $key, $value = '' ) {
469
 
470
  if ( ! $key ) {
471
+ $this->_doing_it_wrong( __METHOD__, 'No valid cache key has been specified.', '3.1.0' );
472
  return false;
473
  }
474
 
484
  */
485
  protected function check_options_reset() {
486
 
487
+ if ( ! $this->is_seo_settings_page( false ) || ! $this->can_access_settings() )
 
 
 
 
 
 
488
  return;
489
 
490
  if ( $this->get_option( 'tsf-settings-reset', false ) ) {
491
+ if ( \update_option( THE_SEO_FRAMEWORK_SITE_OPTIONS, $this->get_default_site_options() ) ) {
492
+ $this->update_static_cache( 'settings_notice', 'reset' );
 
493
  } else {
494
+ $this->update_static_cache( 'settings_notice', 'error' );
 
495
  }
496
+ $this->admin_redirect( $this->seo_settings_page_slug );
497
+ exit;
498
  }
499
  }
500
 
501
  /**
502
+ * Updates a single SEO option.
503
  *
504
  * Can return false if option is unchanged.
505
  *
506
  * @since 2.9.0
507
  *
508
+ * @param string $key The option key.
509
  * @param string $value The option value.
510
  * @return bool True on success, false on failure.
511
  */
520
  }
521
 
522
  /**
523
+ * Allows bulk-updating of the SEO settings.
524
  *
525
  * @since 2.7.0
526
  *
527
+ * @param string|array $new_option : {
528
  * if string: The string will act as a key for a new empty string option, e.g. : {
529
  * 'sitemap_index' becomes ['sitemap_index' => '']
530
  * }
532
  * ['sitemap_index' => 1]
533
  * }
534
  * }
535
+ * @param string $settings_field The Settings Field to update. Defaults
536
+ * to The SEO Framework settings field.
537
  * @return bool True on success. False on failure.
538
  */
539
+ public function update_settings( $new_option = '', $settings_field = '' ) {
540
 
541
  if ( ! $settings_field ) {
542
+ $settings_field = THE_SEO_FRAMEWORK_SITE_OPTIONS;
543
  $this->init_sanitizer_filters();
544
  }
545
 
546
+ $settings = \wp_parse_args( $new_option, \get_option( $settings_field ) );
547
 
548
  return \update_option( $settings_field, $settings );
549
  }
560
  * @staticvar array $defaults_cache
561
  * @uses $this->get_default_site_options()
562
  *
563
+ * @param string $key Required. The option name.
564
+ * @param string $depr Deprecated. Leave empty.
565
+ * @param bool $use_cache Optional. Whether to use the options cache or not.
566
  * @return mixed default option
567
  * null If option doesn't exist.
568
  */
593
  * @since 2.3.4
594
  * @since 3.1.0 : Now returns 0 if the option doesn't exist, instead of -1.
595
  * @staticvar array $warned_cache
596
+ * @uses THE_SEO_FRAMEWORK_SITE_OPTIONS
597
  * @uses $this->get_warned_site_options()
598
  *
599
+ * @param string $key Required. The option name.
600
+ * @param string $depr Deprecated. Leave empty.
601
+ * @param bool $use_cache Optional. Whether to use the options cache or not.
602
  * @return int 0|1 Whether the option is flagged as dangerous for SEO.
603
  */
604
  public function get_warned_settings( $key, $depr = '', $use_cache = true ) {
632
  * @return string
633
  */
634
  public function get_robots_post_type_option_id( $type ) {
635
+ return $this->s_field_id( $type . '_post_types' );
636
  }
637
 
638
  /**
639
  * Returns Facebook locales array values.
640
  *
641
  * @since 2.5.2
642
+ * @see https://www.facebook.com/translations/FacebookLocales.xml (deprecated)
643
+ * @see https://wordpress.org/support/topic/oglocale-problem/#post-11456346
644
+ * mirror: http://web.archive.org/web/20190601043836/https://wordpress.org/support/topic/oglocale-problem/
645
  * @see $this->language_keys() for the associative array keys.
646
  *
647
  * @return array Valid Facebook locales
798
  * Use this to compare the numeric key position.
799
  *
800
  * @since 2.5.2
801
+ * @see https://www.facebook.com/translations/FacebookLocales.xml (deprecated)
802
+ * @see https://wordpress.org/support/topic/oglocale-problem/#post-11456346
803
+ * mirror: http://web.archive.org/web/20190601043836/https://wordpress.org/support/topic/oglocale-problem/
804
  *
805
  * @return array Valid Facebook locale keys
806
  */
inc/classes/sitemaps.class.php DELETED
@@ -1,1346 +0,0 @@
1
- <?php
2
- /**
3
- * @package The_SEO_Framework\Classes
4
- */
5
- namespace The_SEO_Framework;
6
-
7
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
8
-
9
- /**
10
- * The SEO Framework plugin
11
- * Copyright (C) 2015 - 2019 Sybre Waaijer, CyberWire (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
- /**
27
- * Class The_SEO_Framework\Sitemaps
28
- *
29
- * Handles sitemap output.
30
- *
31
- * @since 2.8.0
32
- */
33
- class Sitemaps extends Metaboxes {
34
-
35
- /**
36
- * Checks if sitemap is being output.
37
- *
38
- * @since 2.5.2
39
- *
40
- * @var bool true if sitemap is being output.
41
- */
42
- protected $doing_sitemap = false;
43
-
44
- /**
45
- * Determines whether we can output sitemap or not based on options and blog status.
46
- *
47
- * @since 2.6.0
48
- * @since 2.9.2 : Now returns true when using plain and ugly permalinks.
49
- * @staticvar bool $cache
50
- *
51
- * @return bool
52
- */
53
- public function can_run_sitemap() {
54
-
55
- static $cache = null;
56
-
57
- if ( isset( $cache ) )
58
- return $cache;
59
-
60
- /**
61
- * Don't do anything on a deleted or spam blog on MultiSite.
62
- * There's nothing to find anyway.
63
- */
64
- return $cache = $this->get_option( 'sitemaps_output' ) && ! $this->current_blog_is_spam_or_deleted();
65
- }
66
-
67
- /**
68
- * Adds rewrite rule to WordPress
69
- * This rule defines the sitemap.xml output
70
- *
71
- * @since 2.2.9
72
- *
73
- * @param bool $force add the rule anyway, regardless of detected environment.
74
- */
75
- public function rewrite_rule_sitemap( $force = false ) {
76
-
77
- //* Adding rewrite rules only has effect when permalink structures are active.
78
- if ( $this->can_run_sitemap() || $force ) {
79
- /**
80
- * Don't do anything if a sitemap plugin is active.
81
- * On sitemap plugin activation, the sitemap plugin should flush the
82
- * rewrite rules. If it doesn't, then this plugin's sitemap will be called.
83
- */
84
- if ( $this->detect_sitemap_plugin() )
85
- return;
86
-
87
- \add_rewrite_rule( 'sitemap\.xml$', 'index.php?the_seo_framework_sitemap=xml', 'top' );
88
- \add_rewrite_rule( 'sitemap\.xsl$', 'index.php?the_seo_framework_sitemap=xsl', 'top' );
89
- }
90
- }
91
-
92
- /**
93
- * Registers the_seo_framework_sitemap to WP_Query.
94
- *
95
- * @since 2.2.9
96
- *
97
- * @param array $vars The WP_Query variables.
98
- * @return array $vars The adjusted vars.
99
- */
100
- public function enqueue_sitemap_query_vars( $vars ) {
101
-
102
- if ( $this->can_run_sitemap() ) {
103
- $vars[] = 'the_seo_framework_sitemap';
104
- }
105
-
106
- return $vars;
107
- }
108
-
109
- /**
110
- * Outputs sitemap.xml 'file' and header on sitemap query.
111
- * Also cleans up globals and sets up variables.
112
- *
113
- * @since 2.2.9
114
- */
115
- public function maybe_output_sitemap() {
116
-
117
- if ( $this->can_run_sitemap() ) {
118
- global $wp_query;
119
-
120
- if ( isset( $wp_query->query_vars['the_seo_framework_sitemap'] ) && 'xml' === $wp_query->query_vars['the_seo_framework_sitemap'] ) {
121
-
122
- $this->validate_sitemap_scheme();
123
-
124
- // Don't let WordPress think this is 404.
125
- $wp_query->is_404 = false;
126
-
127
- $this->doing_sitemap = true;
128
-
129
- /**
130
- * Set at least 2000 variables free.
131
- * Freeing 0.15MB on a clean WordPress installation.
132
- * @since 2.6.0
133
- */
134
- $this->clean_up_globals_for_sitemap();
135
-
136
- $this->output_sitemap();
137
- }
138
- }
139
- }
140
-
141
- /**
142
- * Verifies whether the requested URI scheme matches the home URL input.
143
- * If it doesn't, it'll redirect the visitor to the set scheme in WordPress home URL settings.
144
- *
145
- * This prevents invalid cached scheme outputs in the sitemap.
146
- *
147
- * NOTE: To alleviate bug reports, we also check for the scheme settings.
148
- * So, if users are experiencing issues with this (they won't), then tell them to
149
- * set a preferred URL scheme.
150
- *
151
- * Normally, WordPress takes care of this via `redirect_canonical()`.
152
- * However, `redirect_canonical()` adds unwanted trailing slashes.
153
- *
154
- * So, we output the sitemap before `redirect_canonical()` fires. Then, we need this to
155
- * prevent incorrect scheme bias when the "automatic" scheme setting is turned on.
156
- * As otherwise, the plugin will cache the sitemap with the invalid scheme.
157
- *
158
- * All this is to prevent bad(ly configured) bots triggering unwanted schemes.
159
- *
160
- * @since 3.1.0
161
- * @TODO consider hijacking get_preferred_scheme() instead.
162
- * @TODO consider hijacking that filter always, making the "automatic" option even more reliable.
163
- */
164
- protected function validate_sitemap_scheme() {
165
-
166
- if ( $this->get_option( 'canonical_scheme' ) !== 'automatic' ) return;
167
-
168
- $wp_scheme = strtolower( parse_url( \get_home_url(), PHP_URL_SCHEME ) );
169
- switch ( $wp_scheme ) {
170
- case 'https':
171
- if ( $this->is_ssl() ) return;
172
- break;
173
-
174
- case 'http':
175
- if ( ! $this->is_ssl() ) return;
176
- break;
177
-
178
- default:
179
- // parse_url failure. Bail.
180
- return;
181
- }
182
-
183
- //? Prevent redirect loop.
184
- $fix_arg = [
185
- 'name' => 'tsfrf', // abbr: tsf redirect fix
186
- 'value' => '1',
187
- ];
188
- if ( empty( $_GET[ $fix_arg['name'] ] )
189
- || $_GET[ $fix_arg['name'] ] != $fix_arg['value'] ) { // loose comparison OK.
190
-
191
- $this->clean_response_header();
192
-
193
- \wp_safe_redirect( \add_query_arg(
194
- $fix_arg['name'],
195
- $fix_arg['value'],
196
- $this->set_url_scheme( $this->get_sitemap_xml_url(), $wp_scheme )
197
- ), 301 );
198
- exit;
199
- }
200
- }
201
-
202
- /**
203
- * Outputs sitemap.xsl 'file' and header on sitemap stylesheet query.
204
- *
205
- * @since 2.2.9
206
- */
207
- public function maybe_output_sitemap_stylesheet() {
208
-
209
- if ( $this->can_run_sitemap() ) {
210
- global $wp_query;
211
-
212
- if ( isset( $wp_query->query_vars['the_seo_framework_sitemap'] ) && 'xsl' === $wp_query->query_vars['the_seo_framework_sitemap'] ) {
213
- // Don't let WordPress think this is 404.
214
- $wp_query->is_404 = false;
215
-
216
- $this->doing_sitemap = true;
217
-
218
- $this->output_sitemap_xsl_stylesheet();
219
- }
220
- }
221
- }
222
-
223
- /**
224
- * Destroys unused $GLOBALS. To be used prior to outputting sitemap.
225
- *
226
- * @since 2.6.0
227
- * @since 2.8.0 Renamed from clean_up_globals().
228
- *
229
- * @param bool $get_freed_memory Whether to return the freed memory in bytes.
230
- * @return int $freed_memory
231
- */
232
- protected function clean_up_globals_for_sitemap( $get_freed_memory = false ) {
233
-
234
- static $freed_memory = null;
235
-
236
- if ( $get_freed_memory )
237
- return $freed_memory;
238
-
239
- $this->the_seo_framework_debug and $memory = memory_get_usage();
240
-
241
- $remove = [
242
- 'wp_filter' => [
243
- 'wp_head',
244
- 'admin_head',
245
- 'the_content',
246
- 'the_content_feed',
247
- 'the_excerpt_rss',
248
- 'wp_footer',
249
- 'admin_footer',
250
- ],
251
- 'wp_registered_widgets',
252
- 'wp_registered_sidebars',
253
- 'wp_registered_widget_updates',
254
- 'wp_registered_widget_controls',
255
- '_wp_deprecated_widgets_callbacks',
256
- 'posts',
257
- 'shortcode_tags',
258
- ];
259
-
260
- foreach ( $remove as $key => $value ) {
261
- if ( is_array( $value ) ) {
262
- foreach ( $value as $v )
263
- unset( $GLOBALS[ $key ][ $v ] );
264
- } else {
265
- unset( $GLOBALS[ $value ] );
266
- }
267
- }
268
-
269
- $this->the_seo_framework_debug and $freed_memory = $memory - memory_get_usage();
270
- }
271
-
272
- /**
273
- * Outputs sitemap.xml 'file' and header.
274
- *
275
- * @since 2.2.9
276
- * @since 3.1.0 1. Now outputs 200-response code.
277
- * 2. Now outputs robots tag, preventing indexing.
278
- * 3. Now overrides other header tags.
279
- */
280
- protected function output_sitemap() {
281
-
282
- //* Remove output, if any.
283
- $this->clean_response_header();
284
-
285
- if ( ! headers_sent() ) {
286
- \status_header( 200 );
287
- header( 'Content-type: text/xml; charset=utf-8', true );
288
- header( 'X-Robots-Tag: noindex, follow', true );
289
- }
290
-
291
- //* Fetch sitemap content and add trailing line. Already escaped internally.
292
- $this->output_sitemap_content();
293
- echo "\n";
294
-
295
- // We're done now.
296
- exit;
297
- }
298
-
299
- /**
300
- * Sitemap XSL stylesheet output.
301
- *
302
- * @since 2.8.0
303
- * @since 3.1.0 1. Now outputs 200-response code.
304
- * 2. Now outputs robots tag, preventing indexing.
305
- * 3. Now overrides other header tags.
306
- */
307
- public function output_sitemap_xsl_stylesheet() {
308
-
309
- $this->clean_response_header();
310
-
311
- if ( ! headers_sent() ) {
312
- \status_header( 200 );
313
- header( 'Content-type: text/xsl; charset=utf-8', true );
314
- header( 'Cache-Control: max-age=1800', true );
315
- header( 'X-Robots-Tag: noindex, follow', true );
316
- }
317
-
318
- $this->get_view( 'sitemap/xsl-stylesheet' );
319
- exit;
320
- }
321
-
322
- /**
323
- * Output sitemap.xml content from transient.
324
- *
325
- * @since 2.8.0
326
- *
327
- * @return string Sitemap XML contents.
328
- */
329
- protected function output_sitemap_content() {
330
-
331
- $this->the_seo_framework_debug and $timer_start = microtime( true );
332
-
333
- /**
334
- * Re-use the variable, eliminating database requests
335
- * @since 2.4.0
336
- */
337
- $sitemap_content = $this->get_option( 'cache_sitemap' ) ? $this->get_transient( $this->get_sitemap_transient_name() ) : false;
338
-
339
- echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
340
- echo $this->get_sitemap_xsl_stylesheet_tag();
341
-
342
- /**
343
- * Output debug prior output.
344
- * @since 2.8.0
345
- */
346
- if ( $this->the_seo_framework_debug ) {
347
- echo '<!-- Site estimated peak usage prior to generation: ' . number_format( memory_get_peak_usage() / 1024 / 1024, 3 ) . ' MB -->' . "\n";
348
- echo '<!-- System estimated peak usage prior to generation: ' . number_format( memory_get_peak_usage( true ) / 1024 / 1024, 3 ) . ' MB -->' . "\n";
349
- }
350
-
351
- echo $this->get_sitemap_urlset_open_tag();
352
- echo $this->setup_sitemap( $sitemap_content );
353
- echo $this->get_sitemap_urlset_close_tag();
354
-
355
- if ( false === $sitemap_content ) {
356
- echo "\n" . '<!-- ' . \esc_html__( 'Sitemap is generated for this view', 'autodescription' ) . ' -->';
357
- } else {
358
- echo "\n" . '<!-- ' . \esc_html__( 'Sitemap is served from cache', 'autodescription' ) . ' -->';
359
- }
360
-
361
- /**
362
- * Output debug info.
363
- * @since 2.3.7
364
- */
365
- if ( $this->the_seo_framework_debug ) {
366
- echo "\n" . '<!-- Site estimated peak usage: ' . number_format( memory_get_peak_usage() / 1024 / 1024, 3 ) . ' MB -->';
367
- echo "\n" . '<!-- System estimated peak usage: ' . number_format( memory_get_peak_usage( true ) / 1024 / 1024, 3 ) . ' MB -->';
368
- echo "\n" . '<!-- Freed memory prior to generation: ' . number_format( $this->clean_up_globals_for_sitemap( true ) / 1024, 3 ) . ' kB -->';
369
- echo "\n" . '<!-- Sitemap generation time: ' . number_format( microtime( true ) - $timer_start, 6 ) . ' seconds -->';
370
- }
371
- }
372
-
373
- /**
374
- * Returns the opening tag for the sitemap URLset.
375
- *
376
- * @since 2.8.0
377
- *
378
- * @return string The sitemap URLset opening tag.
379
- */
380
- public function get_sitemap_urlset_open_tag() {
381
-
382
- $schemas = [
383
- 'xmlns' => 'http://www.sitemaps.org/schemas/sitemap/0.9',
384
- 'xmlns:xhtml' => 'http://www.w3.org/1999/xhtml',
385
- 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
386
- 'xsi:schemaLocation' => [
387
- 'http://www.sitemaps.org/schemas/sitemap/0.9',
388
- 'http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd',
389
- ],
390
- ];
391
-
392
- /**
393
- * @since 2.8.0
394
- * @param array $schemas The schema list. URLs are expected to be escaped.
395
- */
396
- $schemas = (array) \apply_filters( 'the_seo_framework_sitemap_schemas', $schemas );
397
-
398
- $urlset = '<urlset';
399
- foreach ( $schemas as $type => $values ) {
400
- $urlset .= ' ' . $type . '="';
401
- if ( is_array( $values ) ) {
402
- $urlset .= implode( ' ', $values );
403
- } else {
404
- $urlset .= $values;
405
- }
406
- $urlset .= '"';
407
- }
408
- $urlset .= '>';
409
-
410
- return $urlset . "\n";
411
- }
412
-
413
- /**
414
- * Returns the closing tag for the sitemap URLset.
415
- *
416
- * @since 2.8.0
417
- *
418
- * @return string The sitemap URLset closing tag.
419
- */
420
- public function get_sitemap_urlset_close_tag() {
421
- return '</urlset>';
422
- }
423
-
424
- /**
425
- * Returns stylesheet XSL location tag.
426
- *
427
- * @since 2.8.0
428
- * @since 2.9.3 Now checks against request to see if there's a domain mismatch.
429
- *
430
- * @return string The sitemap XSL location tag.
431
- */
432
- public function get_sitemap_xsl_stylesheet_tag() {
433
-
434
- if ( $this->get_option( 'sitemap_styles' ) ) {
435
-
436
- $url = \esc_url( $this->get_sitemap_xsl_url(), [ 'http', 'https' ] );
437
-
438
- if ( ! empty( $_SERVER['HTTP_HOST'] ) ) {
439
- $_parsed = \wp_parse_url( $url );
440
- $_r_parsed = \wp_parse_url(
441
- \esc_url(
442
- \wp_unslash( $_SERVER['HTTP_HOST'] ),
443
- [ 'http', 'https' ]
444
- )
445
- ); // sanitization ok: esc_url is esc_url_raw with a bowtie.
446
-
447
- if ( isset( $_parsed['host'], $_r_parsed['host'] ) )
448
- if ( $_parsed['host'] !== $_r_parsed['host'] )
449
- return '';
450
- }
451
-
452
- return sprintf( '<?xml-stylesheet type="text/xsl" href="%s"?>', $url ) . "\n";
453
- }
454
-
455
- return '';
456
- }
457
-
458
- /**
459
- * Returns the stylesheet XSL location URL.
460
- *
461
- * @since 2.8.0
462
- * @since 3.0.0 1: No longer uses home URL from cache. But now uses `get_home_url()`.
463
- * 2: Now takes query parameters (if any) and restores them correctly.
464
- * @global \WP_Rewrite $wp_rewrite
465
- *
466
- * @return string URL location of the XSL stylesheet. Unescaped.
467
- */
468
- public function get_sitemap_xsl_url() {
469
- global $wp_rewrite;
470
-
471
- $home = $this->set_url_scheme( \get_home_url() );
472
-
473
- $parsed = parse_url( $home );
474
- $query = isset( $parsed['query'] ) ? $parsed['query'] : '';
475
-
476
- if ( $query )
477
- $home = str_replace( '?' . $query, '', $home );
478
-
479
- $home = \trailingslashit( $home );
480
-
481
- if ( $wp_rewrite->using_index_permalinks() ) {
482
- $loc = $home . 'index.php/sitemap.xsl';
483
- } elseif ( $wp_rewrite->using_permalinks() ) {
484
- $loc = $home . 'sitemap.xsl';
485
- } else {
486
- $loc = $home . '?the_seo_framework_sitemap=xsl';
487
- }
488
-
489
- if ( $query )
490
- $loc = $this->append_php_query( $loc, $query );
491
-
492
- return $loc;
493
- }
494
-
495
- /**
496
- * Returns the sitemap XML location URL.
497
- *
498
- * @since 2.9.2
499
- * @since 3.0.0 1: No longer uses home URL from cache. But now uses `get_home_url()`.
500
- * 2: Now takes query parameters (if any) and restores them correctly.
501
- * @global \WP_Rewrite $wp_rewrite
502
- *
503
- * @return string URL location of the XML sitemap. Unescaped.
504
- */
505
- public function get_sitemap_xml_url() {
506
- global $wp_rewrite;
507
-
508
- $home = $this->set_url_scheme( \get_home_url() );
509
-
510
- $parsed = parse_url( $home );
511
- $query = isset( $parsed['query'] ) ? $parsed['query'] : '';
512
-
513
- if ( $query )
514
- $home = str_replace( '?' . $query, '', $home );
515
-
516
- $home = \trailingslashit( $home );
517
-
518
- if ( $query )
519
- $home = str_replace( '?' . $query, '', $home );
520
-
521
- if ( $wp_rewrite->using_index_permalinks() ) {
522
- $loc = $home . 'index.php/sitemap.xml';
523
- } elseif ( $wp_rewrite->using_permalinks() ) {
524
- $loc = $home . 'sitemap.xml';
525
- } else {
526
- $loc = $home . '?the_seo_framework_sitemap=xml';
527
- }
528
-
529
- if ( $query )
530
- $loc = $this->append_php_query( $loc, $query );
531
-
532
- return $loc;
533
- }
534
-
535
- /**
536
- * Returns the robots.txt location URL.
537
- * Only allows root domains.
538
- *
539
- * @since 2.9.2
540
- * @global \WP_Rewrite $wp_rewrite
541
- *
542
- * @return string URL location of robots.txt. Unescaped.
543
- */
544
- public function get_robots_txt_url() {
545
- global $wp_rewrite;
546
-
547
- if ( $wp_rewrite->using_permalinks() && ! $this->is_subdirectory_installation() ) {
548
- $home = \trailingslashit( $this->set_url_scheme( $this->get_home_host() ) );
549
- $loc = $home . 'robots.txt';
550
- } else {
551
- $loc = '';
552
- }
553
-
554
- return $loc;
555
- }
556
-
557
- /**
558
- * Create sitemap.xml content transient.
559
- *
560
- * @since 2.6.0
561
- * @since 3.0.6 Now only sets transient when the option is checked.
562
- *
563
- * @param string|bool $content required The sitemap transient content.
564
- * @return string The sitemap content.
565
- */
566
- public function setup_sitemap( $sitemap_content = false ) {
567
-
568
- if ( false === $sitemap_content ) {
569
- //* Transient doesn't exist yet.
570
- $sitemap_content = $this->generate_sitemap();
571
-
572
- /**
573
- * Transient expiration: 1 week.
574
- * Keep the sitemap for at most 1 week.
575
- */
576
- $expiration = WEEK_IN_SECONDS;
577
-
578
- if ( $this->get_option( 'cache_sitemap' ) )
579
- $this->set_transient( $this->get_sitemap_transient_name(), $sitemap_content, $expiration );
580
- }
581
-
582
- return $sitemap_content;
583
- }
584
-
585
- /**
586
- * Generate sitemap.xml content.
587
- *
588
- * @since 2.2.9
589
- * @since 2.8.0 Now adjusts memory limit when possible.
590
- * @since 2.9.3 No longer crashes on WordPress sites below WP 4.6.
591
- * @since 3.0.4 No longer outputs empty URL entries.
592
- * @since 3.1.0 1. Removed the WP<4.6 function_exists check.
593
- * 2. Now uses WordPress' built-in memory raiser function, with "context" sitemap.
594
- *
595
- * @return string The sitemap content.
596
- */
597
- protected function generate_sitemap() {
598
-
599
- \wp_raise_memory_limit( 'sitemap' );
600
-
601
- $content = '';
602
-
603
- $total_post_limit = $this->get_sitemap_post_limit();
604
-
605
- /**
606
- * Maximum pages, posts and cpt to fetch.
607
- * A total of 3600, consisting of 3 times $total_post_limit.
608
- *
609
- * @since 2.2.9
610
- * TODO remove?
611
- * @param int $totalpages
612
- * @param int $totalposts
613
- * @param int $total_cpt_posts
614
- */
615
- $totalpages = (int) \apply_filters( 'the_seo_framework_sitemap_pages_count', $total_post_limit );
616
- $totalposts = (int) \apply_filters( 'the_seo_framework_sitemap_posts_count', $total_post_limit );
617
- $total_cpt_posts = (int) \apply_filters( 'the_seo_framework_sitemap_custom_posts_count', $total_post_limit );
618
-
619
- $noindex_post_types = $this->get_option( $this->get_robots_post_type_option_id( 'noindex' ) );
620
-
621
- if ( ! empty( $noindex_post_types['page'] ) ) {
622
- $totalpages = 0;
623
- }
624
- if ( ! empty( $noindex_post_types['post'] ) ) {
625
- $totalposts = 0;
626
- }
627
-
628
- $latest_pages = [];
629
- $latest_posts = [];
630
- $latest_cpt_posts = [];
631
- $cpt = [];
632
-
633
- //* Sets timezone according to WordPress settings.
634
- $this->set_timezone();
635
- $timestamp_format = $this->get_timestamp_format();
636
-
637
- $show_priority = (bool) $this->get_option( 'sitemaps_priority' );
638
- $show_modified = (bool) $this->get_option( 'sitemaps_modified' );
639
-
640
- /**
641
- * @since 2.2.9
642
- * @param bool $timestamp Whether to display the timestamp.
643
- */
644
- $timestamp = (bool) \apply_filters( 'the_seo_framework_sitemap_timestamp', true );
645
-
646
- if ( $timestamp )
647
- $content .= sprintf(
648
- '<!-- %s -->',
649
- sprintf(
650
- /* translators: %s = timestamp */
651
- \esc_html__( 'Sitemap is generated on %s', 'autodescription' ),
652
- \current_time( 'Y-m-d H:i:s \G\M\T' )
653
- )
654
- ) . "\n";
655
-
656
- $wp_query = new \WP_Query;
657
- $wp_query->init();
658
- $query = $wp_query->query = $wp_query->query_vars = [];
659
-
660
- if ( $totalpages ) {
661
- //* Ascend by the date for normal pages. Older pages get to the top of the list.
662
- $defaults = [
663
- 'posts_per_page' => $totalpages,
664
- 'post_type' => 'page',
665
- 'orderby' => 'date',
666
- 'order' => 'ASC',
667
- 'post_status' => 'publish',
668
- 'has_password' => false,
669
- 'fields' => 'ids',
670
- 'cache_results' => false,
671
- 'suppress_filters' => false,
672
- 'no_found_rows' => true,
673
- ];
674
-
675
- /**
676
- * @since 2.8.0
677
- * @since 3.0.6: $args['suppress_filters'] now defaults to false.
678
- * @param array $args The new query arguments.
679
- * @param array $defaults The default query arguments
680
- */
681
- $args = \apply_filters( 'the_seo_framework_sitemap_pages_query_args', [], $defaults );
682
-
683
- $wp_query->query = $wp_query->query_vars = \wp_parse_args( $args, $defaults );
684
- $latest_pages = $wp_query->get_posts();
685
- }
686
- $latest_pages_amount = count( $latest_pages );
687
-
688
- if ( $latest_pages_amount > 0 ) :
689
-
690
- $page_on_front = $this->has_page_on_front();
691
- $page_on_front_id = (int) \get_option( 'page_on_front' );
692
- $page_for_posts_id = (int) \get_option( 'page_for_posts' );
693
-
694
- $id_on_front = $page_on_front ? $page_on_front_id : $page_for_posts_id;
695
-
696
- //* Remove ID on front from list and add frontpage to list.
697
- if ( $page_on_front && false !== $key_on_front = array_search( $id_on_front, $latest_pages, true ) ) {
698
- unset( $latest_pages[ $key_on_front ] );
699
- }
700
-
701
- //= Render frontpage.
702
- $front_page = $page_on_front ? \get_post( $id_on_front ) : null;
703
- $render_front = false;
704
- if ( ! $this->get_option( 'homepage_noindex' ) ) {
705
- if ( $page_on_front ) {
706
- $render_front = isset( $front_page->ID )
707
- && $this->is_post_included_in_sitemap( $front_page->ID )
708
- && ! $this->is_protected( $front_page->ID );
709
- } else {
710
- $render_front = $this->is_post_included_in_sitemap( $id_on_front );
711
- }
712
- }
713
- if ( $render_front ) {
714
- $_url = $this->get_homepage_permalink();
715
- if ( $_url ) {
716
- $content .= "\t<url>\n";
717
- $content .= "\t\t<loc>" . $_url . "</loc>\n";
718
-
719
- if ( $show_modified ) {
720
- if ( $page_on_front ) {
721
- $front_modified_gmt = isset( $front_page->post_modified_gmt ) ? $front_page->post_modified_gmt : '0000-00-00 00:00:00';
722
- } else {
723
- $args = [
724
- 'numberposts' => 1,
725
- 'post_type' => 'post',
726
- 'post_status' => 'publish',
727
- 'has_password' => false,
728
- 'orderby' => 'post_date',
729
- 'order' => 'DESC',
730
- 'offset' => 0,
731
- ];
732
- $latests_posts = \wp_get_recent_posts( $args, OBJECT );
733
- $latest_post = isset( $latests_posts[0] ) ? $latests_posts[0] : null;
734
- $front_modified_gmt = isset( $latest_post->post_date_gmt ) ? $latest_post->post_date_gmt : '0000-00-00 00:00:00';
735
- }
736
-
737
- if ( '0000-00-00 00:00:00' !== $front_modified_gmt )
738
- $content .= "\t\t<lastmod>" . $this->gmt2date( $timestamp_format, $front_modified_gmt ) . "</lastmod>\n";
739
- }
740
-
741
- if ( $show_priority ) {
742
- $content .= "\t\t<priority>1.0</priority>\n";
743
- }
744
- $content .= "\t</url>\n";
745
- }
746
- //* Free memory.
747
- unset( $latests_posts, $latest_post, $front_page );
748
- }
749
-
750
- //= Render the page for posts.
751
- if ( $page_on_front && $page_for_posts_id ) :
752
- //* Remove ID for blog from list and add frontpage to list.
753
- if ( false !== $key_for_posts = array_search( $page_for_posts_id, $latest_pages, true ) ) {
754
- unset( $latest_pages[ $key_for_posts ] );
755
- }
756
-
757
- $blog_page = \get_post( $page_for_posts_id );
758
- $render_blog = isset( $blog_page->ID )
759
- && $this->is_post_included_in_sitemap( $blog_page->ID )
760
- && ! $this->is_protected( $blog_page->ID );
761
-
762
- if ( $render_blog ) {
763
- $_url = $this->create_canonical_url( [ 'id' => $blog_page->ID ] );
764
- if ( $_url ) {
765
- $content .= "\t<url>\n";
766
- $content .= "\t\t<loc>" . $_url . "</loc>\n";
767
-
768
- if ( $show_modified ) {
769
- $args = [
770
- 'numberposts' => 1,
771
- 'post_type' => 'post',
772
- 'post_status' => 'publish',
773
- 'has_password' => false,
774
- 'orderby' => 'post_date',
775
- 'order' => 'DESC',
776
- 'offset' => 0,
777
- ];
778
- $lastest_posts = \wp_get_recent_posts( $args, OBJECT );
779
- $lastest_post = isset( $lastest_posts[0] ) ? $lastest_posts[0] : null;
780
- $latest_post_published_gmt = isset( $lastest_post->post_date_gmt ) ? $lastest_post->post_date_gmt : '0000-00-00 00:00:00';
781
- $page_for_posts_modified_gmt = $blog_page->post_modified_gmt;
782
-
783
- if ( strtotime( $latest_post_published_gmt ) > strtotime( $page_for_posts_modified_gmt ) ) {
784
- $page_modified_gmt = $latest_post_published_gmt;
785
- } else {
786
- $page_modified_gmt = $page_for_posts_modified_gmt;
787
- }
788
-
789
- if ( '0000-00-00 00:00:00' !== $page_modified_gmt )
790
- $content .= "\t\t<lastmod>" . $this->gmt2date( $timestamp_format, $page_modified_gmt ) . "</lastmod>\n";
791
- }
792
-
793
- if ( $show_priority ) {
794
- $content .= "\t\t<priority>0.9</priority>\n";
795
- }
796
- $content .= "\t</url>\n";
797
- }
798
- }
799
-
800
- //* Free memory.
801
- unset( $latest_posts, $latest_post, $blog_page );
802
- endif;
803
-
804
- foreach ( $latest_pages as $page_id ) :
805
- $page = \get_post( $page_id );
806
- if ( empty( $page->ID ) || ! $this->is_post_included_in_sitemap( $page->ID ) )
807
- continue;
808
-
809
- $_url = $this->create_canonical_url( [ 'id' => $page->ID ] );
810
- if ( ! $_url )
811
- continue;
812
-
813
- $content .= "\t<url>\n";
814
- $content .= "\t\t<loc>" . $_url . "</loc>\n";
815
-
816
- if ( $show_modified ) {
817
- $page_modified_gmt = $page->post_modified_gmt;
818
-
819
- if ( '0000-00-00 00:00:00' !== $page_modified_gmt )
820
- $content .= "\t\t<lastmod>" . $this->gmt2date( $timestamp_format, $page_modified_gmt ) . "</lastmod>\n";
821
- }
822
-
823
- if ( $show_priority ) {
824
- $content .= "\t\t<priority>0.9</priority>\n";
825
- }
826
- $content .= "\t</url>\n";
827
- endforeach;
828
-
829
- //* Free memory.
830
- unset( $latest_pages, $page );
831
- endif;
832
-
833
- if ( $totalposts ) {
834
- //* Descend by the date for posts. The latest posts get to the top of the list after pages.
835
- $defaults = [
836
- 'posts_per_page' => $totalposts,
837
- 'post_type' => 'post',
838
- 'orderby' => 'date',
839
- 'order' => 'DESC',
840
- 'post_status' => 'publish',
841
- 'has_password' => false,
842
- 'fields' => 'ids',
843
- 'cache_results' => false,
844
- 'suppress_filters' => false,
845
- 'no_found_rows' => true,
846
- ];
847
-
848
- /**
849
- * @since 2.8.0
850
- * @since 3.0.6: $args['suppress_filters'] now defaults to false.
851
- * @param array $args The new query arguments.
852
- * @param array $defaults The default query arguments
853
- */
854
- $args = \apply_filters( 'the_seo_framework_sitemap_posts_query_args', [], $defaults );
855
-
856
- $wp_query->query = $wp_query->query_vars = \wp_parse_args( $args, $defaults );
857
- $latest_posts = $wp_query->get_posts();
858
- }
859
- $latest_posts_amount = count( $latest_posts );
860
-
861
- if ( $latest_posts_amount > 0 ) :
862
- /**
863
- * Setting up priorities, with pages always being important.
864
- *
865
- * From there, older posts get a gradually lower priority. Down to 0.
866
- * Differentiate with 1 / max posts (0 to $this->max_posts). With a 1 dot decimal.
867
- */
868
- $priority = 0.9;
869
-
870
- /**
871
- * Infinity is abstract. But what is it when it's both positive and negative?
872
- * Undefined. Bugfix.
873
- *
874
- * @since 2.3.2
875
- * @thanks Schlock | https://wordpress.org/support/topic/sitemap-xml-parsing-error
876
- */
877
- $prioritydiff = 0;
878
-
879
- if ( $latest_posts_amount > 1 )
880
- $prioritydiff = 0.9 / $latest_posts_amount;
881
-
882
- // Keep it consistent. Only remove 0.1 when we only have a few posts.
883
- if ( $latest_posts_amount <= 9 && $latest_posts_amount > 1 )
884
- $prioritydiff = 0.1;
885
-
886
- /**
887
- * This can be heavy.
888
- */
889
- foreach ( $latest_posts as $post_id ) :
890
- $post = \get_post( $post_id );
891
- if ( empty( $post->ID ) || ! $this->is_post_included_in_sitemap( $post->ID ) )
892
- continue;
893
-
894
- $_url = $this->create_canonical_url( [ 'id' => $post->ID ] );
895
- if ( ! $_url )
896
- continue;
897
-
898
- $content .= "\t<url>\n";
899
- // No need to use static vars
900
- $content .= "\t\t<loc>" . $_url . "</loc>\n";
901
-
902
- if ( $show_modified ) {
903
- $post_modified_gmt = $post->post_modified_gmt;
904
-
905
- if ( '0000-00-00 00:00:00' !== $post_modified_gmt )
906
- $content .= "\t\t<lastmod>" . $this->gmt2date( $timestamp_format, $post_modified_gmt ) . "</lastmod>\n";
907
- }
908
-
909
- if ( $show_priority ) {
910
- $content .= "\t\t<priority>" . number_format( $priority, 1 ) . "</priority>\n";
911
-
912
- // Lower the priority for the next pass.
913
- $priority = $priority - $prioritydiff;
914
-
915
- // Cast away negative numbers.
916
- $priority = $priority <= 0 ? 0 : (float) $priority;
917
- }
918
- $content .= "\t</url>\n";
919
- endforeach;
920
-
921
- //* Free memory.
922
- unset( $latest_posts, $post );
923
- endif;
924
-
925
- if ( $total_cpt_posts ) :
926
- // TODO: Use only this loop, instead of separated page/post loops? See $not_cpt var.
927
-
928
- /**
929
- * @since 2.5.0
930
- * @param array $excluded_cpt The excluded custom post types.
931
- */
932
- $excluded_cpt = (array) \apply_filters( 'the_seo_framework_sitemap_exclude_cpt', [] );
933
-
934
- $not_cpt = [ 'post', 'page', 'attachment' ];
935
-
936
- foreach ( $this->get_supported_post_types() as $post_type ) {
937
- if ( ! in_array( $post_type, $not_cpt, true ) ) {
938
- if ( empty( $excluded_cpt ) || ! in_array( $post_type, $excluded_cpt, true ) ) {
939
- if ( empty( $noindex_post_types[ $post_type ] ) )
940
- $cpt[] = $post_type;
941
- }
942
- }
943
- }
944
-
945
- if ( $cpt ) {
946
- //* Descend by the date for CPTs. The latest posts get to the top of the list after pages.
947
- $defaults = [
948
- 'posts_per_page' => $total_cpt_posts,
949
- 'post_type' => $cpt,
950
- 'orderby' => 'date',
951
- 'order' => 'DESC',
952
- 'post_status' => 'publish',
953
- 'has_password' => false,
954
- 'fields' => 'ids',
955
- 'cache_results' => false,
956
- 'suppress_filters' => false,
957
- 'no_found_rows' => true,
958
- ];
959
-
960
- /**
961
- * @since 2.8.0
962
- * @since 3.0.6: $args['suppress_filters'] now defaults to false.
963
- * @param array $args The new query arguments.
964
- * @param array $defaults The default query arguments
965
- */
966
- $args = \apply_filters( 'the_seo_framework_sitemap_cpt_query_args', [], $defaults );
967
-
968
- $wp_query->query = $wp_query->query_vars = \wp_parse_args( $args, $defaults );
969
- $latest_cpt_posts = $wp_query->get_posts();
970
- }
971
- endif;
972
- $latest_cpt_posts_amount = count( $latest_cpt_posts );
973
-
974
- if ( $latest_cpt_posts_amount > 0 ) :
975
-
976
- /**
977
- * Setting up priorities, with pages always being important.
978
- *
979
- * From there, older posts get a gradually lower priority. Down to 0.
980
- * Differentiate with 1 / max posts (0 to $this->max_posts). With a 1 dot decimal.
981
- */
982
- $priority_cpt = 0.9;
983
-
984
- $prioritydiff_cpt = 0;
985
-
986
- if ( $latest_cpt_posts_amount > 1 )
987
- $prioritydiff_cpt = 0.9 / $latest_cpt_posts_amount;
988
-
989
- // Keep it consistent. Only remove 0.1 when we only have a few posts.
990
- if ( $latest_cpt_posts_amount <= 9 && $latest_cpt_posts_amount > 1 )
991
- $prioritydiff_cpt = 0.1;
992
-
993
- /**
994
- * This can be heavy.
995
- */
996
- foreach ( $latest_cpt_posts as $ctp_post_id ) :
997
- $ctp_post = \get_post( $ctp_post_id );
998
- if ( empty( $ctp_post->ID ) || ! $this->is_post_included_in_sitemap( $ctp_post->ID ) )
999
- continue;
1000
-
1001
- $_url = $this->create_canonical_url( [ 'id' => $ctp_post->ID ] );
1002
- if ( ! $_url )
1003
- continue;
1004
-
1005
- $content .= "\t<url>\n";
1006
- //* No need to use static vars
1007
- $content .= "\t\t<loc>" . $_url . "</loc>\n";
1008
-
1009
- if ( $show_modified ) {
1010
- $cpt_modified_gmt = $ctp_post->post_modified_gmt;
1011
- //* Some CPT don't set modified time.
1012
- if ( '0000-00-00 00:00:00' !== $cpt_modified_gmt )
1013
- $content .= "\t\t<lastmod>" . $this->gmt2date( $timestamp_format, $cpt_modified_gmt ) . "</lastmod>\n";
1014
- }
1015
-
1016
- if ( $show_priority ) {
1017
- $content .= "\t\t<priority>" . number_format( $priority_cpt, 1 ) . "</priority>\n";
1018
-
1019
- // Lower the priority for the next pass.
1020
- $priority_cpt = $priority_cpt - $prioritydiff_cpt;
1021
-
1022
- // Cast away negative numbers.
1023
- $priority_cpt = $priority_cpt <= 0 ? 0 : (float) $priority_cpt;
1024
- }
1025
- $content .= "\t</url>\n";
1026
- endforeach;
1027
-
1028
- //* Free memory.
1029
- unset( $latest_cpt_posts, $ctp_post );
1030
- endif;
1031
-
1032
- /**
1033
- * @since 2.5.2
1034
- * @since 3.2.2 Invalid URLs are now skipped.
1035
- * @example return value: [ 'http://example.com' => [ 'lastmod' => '14-01-2018', 'priority' => 0.9 ] ]
1036
- * @param array $custom_urls : {
1037
- * @param string (key) $url The absolute url to the page. : array {
1038
- * @param string $lastmod UNIXTIME Last modified date, e.g. "2016-01-26 13:04:55"
1039
- * @param float|int|string $priority URL Priority
1040
- * }
1041
- * }
1042
- */
1043
- $custom_urls = (array) \apply_filters( 'the_seo_framework_sitemap_additional_urls', [] );
1044
-
1045
- if ( $custom_urls ) {
1046
-
1047
- foreach ( $custom_urls as $url => $args ) {
1048
-
1049
- if ( ! is_array( $args ) ) {
1050
- //* If there are no args, it's assigned as URL (per example)
1051
- $url = $args;
1052
- }
1053
-
1054
- $_url = \esc_url_raw( $url, [ 'http', 'https' ] );
1055
-
1056
- if ( ! $_url ) continue;
1057
-
1058
- $content .= "\t<url>\n";
1059
- //* No need to use static vars
1060
- $content .= "\t\t<loc>" . $_url . "</loc>\n";
1061
-
1062
- if ( isset( $args['lastmod'] ) && $args['lastmod'] ) {
1063
- $content .= "\t\t<lastmod>" . \mysql2date( $timestamp_format, $args['lastmod'], false ) . "</lastmod>\n";
1064
- }
1065
-
1066
- if ( $show_priority ) {
1067
- if ( isset( $args['priority'] ) && $args['priority'] ) {
1068
- $priority = $args['priority'];
1069
- } else {
1070
- $priority = 0.9;
1071
- }
1072
- $content .= "\t\t<priority>" . number_format( $priority, 1 ) . "</priority>\n";
1073
- }
1074
- $content .= "\t</url>\n";
1075
- }
1076
- }
1077
-
1078
- /**
1079
- * @since 2.5.2
1080
- * @param string $extend Custom sitemap extension. Must be escaped.
1081
- */
1082
- $extend = (string) \apply_filters( 'the_seo_framework_sitemap_extend', '' );
1083
-
1084
- if ( $extend )
1085
- $content .= "\t" . $extend . "\n";
1086
-
1087
- //* Reset timezone to default.
1088
- $this->reset_timezone();
1089
-
1090
- return $content;
1091
- }
1092
-
1093
- /**
1094
- * Determines if post is possibly included in the sitemap.
1095
- *
1096
- * This is a weak check, as the filter might not be present outside of the
1097
- * sitemap's scope.
1098
- * The URL also isn't checked, nor the position.
1099
- *
1100
- * @since 3.0.4
1101
- * @since 3.0.6 First filter value now works as intended.
1102
- * @since 3.1.0 1. Resolved a PHP notice when ID is 0, resulting in returning false-esque unintentionally.
1103
- * 2. Now accepts 0 in the filter.
1104
- *
1105
- * @param int $id The post ID to check. When 0, the custom field will not be checked.
1106
- * @return bool True if included, false otherwise.
1107
- */
1108
- public function is_post_included_in_sitemap( $id ) {
1109
-
1110
- static $excluded = null;
1111
- if ( null === $excluded ) {
1112
- /**
1113
- * @since 2.5.2
1114
- * @since 2.8.0 : No longer accepts '0' as entry.
1115
- * @since 3.1.0 : '0' is accepted again.
1116
- * @param array $excluded Sequential list of excluded IDs: [ int ...post_id ]
1117
- */
1118
- $excluded = (array) \apply_filters( 'the_seo_framework_sitemap_exclude_ids', [] );
1119
-
1120
- if ( empty( $excluded ) ) {
1121
- $excluded = [];
1122
- } else {
1123
- $excluded = array_flip( $excluded );
1124
- }
1125
- }
1126
-
1127
- // If it's not in the exclusion list, set it to true.
1128
- $included = ! isset( $excluded[ $id ] );
1129
-
1130
- if ( $included && $id ) {
1131
- // If it's indexed, keep it true.
1132
- $included = ! $this->get_custom_field( '_genesis_noindex', $id );
1133
- }
1134
-
1135
- return $included;
1136
- }
1137
-
1138
- /**
1139
- * Ping search engines on post publish.
1140
- *
1141
- * @since 2.2.9
1142
- * @since 2.8.0 Only worked when the blog was not public...
1143
- * @since 3.1.0 Now allows one ping per language.
1144
- * @uses $this->add_cache_key_suffix()
1145
- * @since 3.2.3 1. Now works as intended again.
1146
- * 2. Removed Easter egg.
1147
- *
1148
- * @return void Early if blog is not public.
1149
- */
1150
- public function ping_searchengines() {
1151
-
1152
- if ( $this->get_option( 'site_noindex' ) || ! $this->is_blog_public() )
1153
- return;
1154
-
1155
- $transient = $this->add_cache_key_suffix( 'tsf_throttle_ping' );
1156
-
1157
- //* NOTE: Use legacy get_transient to prevent ping spam.
1158
- if ( false === \get_transient( $transient ) ) {
1159
- //* Transient doesn't exist yet.
1160
-
1161
- if ( $this->get_option( 'ping_google' ) )
1162
- $this->ping_google();
1163
-
1164
- if ( $this->get_option( 'ping_bing' ) )
1165
- $this->ping_bing();
1166
-
1167
- /**
1168
- * @since 2.5.1
1169
- * @param int $expiration The minimum time between two pings.
1170
- */
1171
- $expiration = (int) \apply_filters( 'the_seo_framework_sitemap_throttle_s', HOUR_IN_SECONDS );
1172
-
1173
- //* @NOTE: Using legacy set_transient to bypass TSF's transient filters and prevent ping spam.
1174
- \set_transient( $transient, 1, $expiration );
1175
- }
1176
- }
1177
-
1178
- /**
1179
- * Pings the sitemap location to Google.
1180
- *
1181
- * @since 2.2.9
1182
- * @since 3.1.0 Updated ping URL. Old one still worked, too.
1183
- * @link https://support.google.com/webmasters/answer/6065812?hl=en
1184
- */
1185
- public function ping_google() {
1186
- $pingurl = 'http://www.google.com/ping?sitemap=' . rawurlencode( $this->get_sitemap_xml_url() );
1187
- \wp_safe_remote_get( $pingurl, [ 'timeout' => 3 ] );
1188
- }
1189
-
1190
- /**
1191
- * Pings the sitemap location to Bing.
1192
- *
1193
- * @since 2.2.9
1194
- * @since 3.2.3 Updated ping URL. Old one still worked, too.
1195
- * @link https://www.bing.com/webmaster/help/how-to-submit-sitemaps-82a15bd4
1196
- */
1197
- public function ping_bing() {
1198
- $pingurl = 'http://www.bing.com/ping?sitemap=' . rawurlencode( $this->get_sitemap_xml_url() );
1199
- \wp_safe_remote_get( $pingurl, [ 'timeout' => 3 ] );
1200
- }
1201
-
1202
- /**
1203
- * Enqueues rewrite rules flush.
1204
- *
1205
- * @since 2.8.0
1206
- */
1207
- public function reinitialize_rewrite() {
1208
-
1209
- if ( $this->get_option( 'sitemaps_output', false ) ) {
1210
- $this->rewrite_rule_sitemap();
1211
- $this->enqueue_rewrite_activate( true );
1212
- } else {
1213
- $this->enqueue_rewrite_deactivate( true );
1214
- }
1215
- }
1216
-
1217
- /**
1218
- * Enqueue rewrite flush for activation.
1219
- *
1220
- * @since 2.3.0
1221
- * @access private
1222
- * @staticvar bool $flush Determines whether a flush is enqueued.
1223
- *
1224
- * @param bool $enqueue Whether to enqueue the flush or return its state.
1225
- * @return bool Whether to flush.
1226
- */
1227
- public function enqueue_rewrite_activate( $enqueue = false ) {
1228
- static $flush = null;
1229
- return $flush ?: $flush = $enqueue;
1230
- }
1231
-
1232
- /**
1233
- * Enqueue rewrite flush for deactivation.
1234
- *
1235
- * @since 2.3.0
1236
- * @access private
1237
- * @staticvar bool $flush Determines whether a flush is enqueued.
1238
- *
1239
- * @param bool $enqueue Whether to enqueue the flush or return its state.
1240
- * @return bool Whether to flush.
1241
- */
1242
- public function enqueue_rewrite_deactivate( $enqueue = false ) {
1243
- static $flush = null;
1244
- return $flush ?: $flush = $enqueue;
1245
- }
1246
-
1247
- /**
1248
- * Flush rewrite rules based on static variables.
1249
- *
1250
- * @since 2.3.0
1251
- * @access private
1252
- */
1253
- public function maybe_flush_rewrite() {
1254
-
1255
- if ( $this->enqueue_rewrite_activate() )
1256
- $this->flush_rewrite_rules_activation();
1257
-
1258
- if ( $this->enqueue_rewrite_deactivate() )
1259
- $this->flush_rewrite_rules_deactivation();
1260
- }
1261
-
1262
- /**
1263
- * Add and Flush rewrite rules on plugin settings change.
1264
- *
1265
- * @since 2.6.6.1
1266
- * @access private
1267
- */
1268
- public function flush_rewrite_rules_activation() {
1269
-
1270
- //* This function is called statically.
1271
- $this->rewrite_rule_sitemap( true );
1272
-
1273
- \flush_rewrite_rules();
1274
- }
1275
-
1276
- /**
1277
- * Flush rewrite rules on settings change.
1278
- *
1279
- * @since 2.6.6.1
1280
- * @since 3.2.2 Now unsets the XSL stylesheet.
1281
- * @access private
1282
- * @global \WP_Rewrite $wp_rewrite
1283
- */
1284
- public function flush_rewrite_rules_deactivation() {
1285
- global $wp_rewrite;
1286
-
1287
- $wp_rewrite->init();
1288
-
1289
- unset( $wp_rewrite->extra_rules_top['sitemap\.xml$'] );
1290
- unset( $wp_rewrite->extra_rules_top['sitemap\.xsl$'] );
1291
-
1292
- $wp_rewrite->flush_rules( true );
1293
- }
1294
-
1295
- /**
1296
- * Returns sitemap color scheme.
1297
- *
1298
- * @since 2.8.0
1299
- *
1300
- * @param bool $get_defaults Whether to get the default colors.
1301
- * @return array The sitemap colors.
1302
- */
1303
- public function get_sitemap_colors( $get_defaults = false ) {
1304
-
1305
- if ( $get_defaults ) {
1306
- $colors = [
1307
- 'main' => '#333',
1308
- 'accent' => '#00cd98',
1309
- ];
1310
- } else {
1311
- $main = $this->s_color_hex( $this->get_option( 'sitemap_color_main' ) );
1312
- $accent = $this->s_color_hex( $this->get_option( 'sitemap_color_accent' ) );
1313
-
1314
- $options = [
1315
- 'main' => $main ? '#' . $main : '',
1316
- 'accent' => $accent ? '#' . $accent : '',
1317
- ];
1318
-
1319
- $options = array_filter( $options );
1320
-
1321
- $colors = array_merge( $this->get_sitemap_colors( true ), $options );
1322
- }
1323
-
1324
- return $colors;
1325
- }
1326
-
1327
- /**
1328
- * Returns the sitemap post query limit.
1329
- *
1330
- * @since 3.1.0
1331
- *
1332
- * @return int The post limit
1333
- */
1334
- protected function get_sitemap_post_limit() {
1335
- /**
1336
- * @since 2.2.9
1337
- * @since 2.8.0 Increased to 1200 from 700.
1338
- * @since 3.1.0 Now returns an option value; it falls back to the default value if not set.
1339
- * @param int $total_post_limit
1340
- */
1341
- return (int) \apply_filters(
1342
- 'the_seo_framework_sitemap_post_limit',
1343
- $this->get_option( 'sitemap_query_limit' )
1344
- );
1345
- }
1346
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/classes/term-data.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -35,29 +37,49 @@ class Term_Data extends Post_Data {
35
  /**
36
  * Initializes term meta data filters and functions.
37
  *
38
- * @since 2.7.0
39
- * @since 3.0.0 No longer checks for admin query.
40
  */
41
- public function initialize_term_meta() {
42
- \add_action( 'edit_term', [ $this, 'update_term_meta' ], 10, 2 );
43
- \add_action( 'delete_term', [ $this, 'delete_term_meta' ], 10, 2 );
44
  }
45
 
46
  /**
47
  * Determines if current query handles term meta.
48
  *
49
  * @since 3.0.0
 
50
  *
51
  * @return bool
52
  */
53
  public function is_term_meta_capable() {
54
- return $this->is_category() || $this->is_tag() || $this->is_tax() || \is_post_type_archive();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  }
56
 
57
  /**
58
  * Returns and caches term meta for the current query.
59
  *
60
  * @since 3.0.0
 
61
  * @staticvar array $cache
62
  *
63
  * @return array The current term meta.
@@ -70,7 +92,7 @@ class Term_Data extends Post_Data {
70
  return $cache;
71
 
72
  if ( $this->is_term_meta_capable() ) {
73
- $cache = $this->get_term_meta( \get_queried_object_id() ) ?: [];
74
  } else {
75
  $cache = [];
76
  }
@@ -86,6 +108,8 @@ class Term_Data extends Post_Data {
86
  * @since 2.8.0 Added filter.
87
  * @since 3.0.0 Added filter.
88
  * @since 3.1.0 Deprecated filter.
 
 
89
  * @staticvar array $cache
90
  *
91
  * @param int $term_id The Term ID.
@@ -99,46 +123,50 @@ class Term_Data extends Post_Data {
99
 
100
  if ( isset( $cache[ $term_id ] ) )
101
  return $cache[ $term_id ];
102
- } else {
103
- $cache = [];
104
  }
105
 
106
- $data = \get_term_meta( $term_id, THE_SEO_FRAMEWORK_TERM_OPTIONS, true );
 
 
 
 
 
 
 
107
 
108
- //* Evaluate merely by presence.
109
- if ( isset( $data['saved_flag'] ) ) {
 
 
 
 
 
 
 
110
  /**
111
  * @since 3.0.0
112
- * @param array $data The CURRENT term data.
 
 
113
  * @param int $term_id The term ID.
114
  */
115
- return $cache[ $term_id ] = \apply_filters( 'the_seo_framework_current_term_meta', $data, $term_id );
116
- }
117
 
118
- static $checked = false;
119
- if ( ! $checked && \has_filter( 'the_seo_framework_get_term_meta' ) ) {
120
- $this->_doing_it_wrong( 'Filter <code>the_seo_framework_get_term_meta</code>', 'the_seo_framework_term_meta_defaults', '3.1.0' );
 
 
 
 
 
 
 
 
 
121
  }
122
- $checked = true;
123
-
124
- /**
125
- * NOTE: Only works before TSF sets its saved-flag. To be used prior to migration.
126
- * Yes, this is inconveniently named. So, we (finally) deprecated it.
127
- * @since 2.8.0
128
- * @since 3.1.0 Now uses the `get_term_meta_defaults()` callback.
129
- * @deprecated. Use `the_seo_framework_term_meta_defaults` instead.
130
- * @param array $data The DEFAULT term data.
131
- * @param int $term_id The current Term ID.
132
- */
133
- $data = \apply_filters_ref_array(
134
- 'the_seo_framework_get_term_meta',
135
- [
136
- $this->get_term_meta_defaults(),
137
- $term_id,
138
- ]
139
- );
140
 
141
- return $cache[ $term_id ] = $data;
142
  }
143
 
144
  /**
@@ -146,106 +174,255 @@ class Term_Data extends Post_Data {
146
  *
147
  * @since 2.7.0
148
  * @since 3.1.0 This is now always used.
 
 
 
 
149
  *
 
150
  * @return array The Term Metadata default options.
151
  */
152
- public function get_term_meta_defaults() {
153
  /**
154
  * @since 2.1.8
155
  * @param array $defaults
 
156
  */
157
- return (array) \apply_filters( 'the_seo_framework_term_meta_defaults', [
158
- 'doctitle' => '',
159
- 'description' => '',
160
- 'noindex' => 0,
161
- 'nofollow' => 0,
162
- 'noarchive' => 0,
163
- 'saved_flag' => 0, // Don't touch, used to prevent data conflict with Genesis.
164
- ] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  }
166
 
167
  /**
168
  * Sanitizes and saves term meta data when a term is altered.
169
  *
170
  * @since 2.7.0
 
 
 
 
 
 
 
 
 
171
  * @securitycheck 3.0.0 OK.
 
 
172
  *
173
- * @param int $term_id Term ID.
174
- * @param int $tt_id Term Taxonomy ID.
175
- * @param string $taxonomy Taxonomy slug
176
- * @return void Early on AJAX call.
177
  */
178
- public function update_term_meta( $term_id, $tt_id, $taxonomy = '' ) {
179
-
180
- if ( $this->doing_ajax() )
181
- return;
182
-
183
- //* Check again against ambiguous injection.
184
- // phpcs:ignore -- wp_unslash() is nonsense.
185
- if ( isset( $_POST['_wpnonce'] ) && \wp_verify_nonce( $_POST['_wpnonce'], 'update-tag_' . $term_id ) ) :
186
-
187
- // phpcs:ignore -- wp_unslash() will ruin intended slashes.
188
- $data = isset( $_POST['autodescription-meta'] ) ? (array) $_POST['autodescription-meta'] : [];
189
- $data = \wp_parse_args( $data, $this->get_term_meta_defaults() );
190
-
191
- foreach ( (array) $data as $key => $value ) :
192
- switch ( $key ) :
193
- case 'doctitle':
194
- $data[ $key ] = $this->s_title_raw( $value );
195
- continue 2;
196
-
197
- case 'description':
198
- $data[ $key ] = $this->s_description_raw( $value );
199
- continue 2;
200
-
201
- case 'noindex':
202
- case 'nofollow':
203
- case 'noarchive':
204
- case 'saved_flag':
205
- $data[ $key ] = $this->s_one_zero( $value );
206
- continue 2;
207
-
208
- default:
209
- // Not implemented for compatibility reasons.
210
- // unset( $data[ $key ] );
211
- break;
212
- endswitch;
213
- endforeach;
214
 
215
- /**
216
- * @since 3.1.0
217
- * @param array $data The data that's going to be saved.
218
- * @param int $term_id Term ID.
219
- * @param int $tt_id Term Taxonomy ID.
220
- * @param string $taxonomy Taxonomy slug
221
- */
222
- $data = (array) \apply_filters_ref_array( 'the_seo_framework_save_term_data', [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  $data,
224
- $term_id,
225
  $tt_id,
226
  $taxonomy,
227
- ] );
 
228
 
229
- \update_term_meta( $term_id, THE_SEO_FRAMEWORK_TERM_OPTIONS, $data );
230
- endif;
231
  }
232
 
233
  /**
234
  * Delete term meta data when a term is deleted.
235
- * Delete only the default data keys.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  *
237
  * @since 2.7.0
 
238
  *
239
  * @param int $term_id Term ID.
240
- * @param int $tt_id Term Taxonomy ID.
241
  */
242
- public function delete_term_meta( $term_id, $tt_id ) {
243
 
244
  //* If this results in an empty data string, all data has already been removed by WP core.
245
  $data = \get_term_meta( $term_id, THE_SEO_FRAMEWORK_TERM_OPTIONS, true );
246
 
247
  if ( is_array( $data ) ) {
248
- foreach ( $this->get_term_meta_defaults() as $key => $value ) {
249
  unset( $data[ $key ] );
250
  }
251
  }
@@ -257,60 +434,14 @@ class Term_Data extends Post_Data {
257
  }
258
  }
259
 
260
- /**
261
- * Tries to fetch a term by $id from query.
262
- *
263
- * @since 2.6.0
264
- * @since 3.0.0 Can now get custom post type objects.
265
- * @todo deprecate
266
- *
267
- * @param int $id The possible taxonomy Term ID.
268
- * @return false|object The Term object.
269
- */
270
- public function fetch_the_term( $id = '' ) {
271
-
272
- static $term = [];
273
-
274
- if ( isset( $term[ $id ] ) )
275
- return $term[ $id ];
276
-
277
- //* Return null if no term can be detected.
278
- if ( false === $this->is_archive() )
279
- return false;
280
-
281
- if ( $this->is_admin() ) {
282
- $taxonomy = $this->get_current_taxonomy();
283
- if ( $taxonomy ) {
284
- $term_id = $id ?: $this->get_the_real_admin_ID();
285
- $term[ $id ] = \get_term_by( 'id', $term_id, $taxonomy );
286
- }
287
- } else {
288
- if ( $this->is_category() || $this->is_tag() ) {
289
- $term[ $id ] = \get_queried_object();
290
- } elseif ( $this->is_tax() ) {
291
- $term[ $id ] = \get_term_by( 'slug', \get_query_var( 'term' ), \get_query_var( 'taxonomy' ) );
292
- } elseif ( \is_post_type_archive() ) {
293
- $post_type = \get_query_var( 'post_type' );
294
- $post_type = is_array( $post_type ) ? reset( $post_type ) : $post_type;
295
-
296
- $term[ $id ] = \get_post_type_object( $post_type );
297
- }
298
- }
299
-
300
- if ( isset( $term[ $id ] ) )
301
- return $term[ $id ];
302
-
303
- return $term[ $id ] = false;
304
- }
305
-
306
  /**
307
  * Returns the taxonomy type object label. Either plural or singular.
308
  *
309
  * @since 3.1.0
310
  * @see $this->get_post_type_label() For the singular alternative.
311
  *
312
- * @param string $post_type The taxonomy type. Required.
313
- * @param bool $singular Wether to get the singlural or plural name.
314
  * @return string The Taxonomy Type name/label, if found.
315
  */
316
  public function get_tax_type_label( $tax_type, $singular = true ) {
@@ -340,9 +471,12 @@ class Term_Data extends Post_Data {
340
  return [];
341
 
342
  $taxonomies = \get_object_taxonomies( $post_type, 'objects' );
343
- $taxonomies = array_filter( $taxonomies, function( $t ) {
344
- return $t->hierarchical;
345
- } );
 
 
 
346
 
347
  switch ( $get ) {
348
  case 'names':
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\Term_Data
4
+ * @subpackage The_SEO_Framework\Data
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
37
  /**
38
  * Initializes term meta data filters and functions.
39
  *
40
+ * @since 4.0.0
 
41
  */
42
+ public function init_term_meta() {
43
+ \add_action( 'edit_term', [ $this, '_update_term_meta' ], 10, 3 );
44
+ \add_action( 'delete_term', [ $this, '_delete_term_meta' ], 10, 3 );
45
  }
46
 
47
  /**
48
  * Determines if current query handles term meta.
49
  *
50
  * @since 3.0.0
51
+ * @since 4.0.0 No longer lists post type archives as term-meta capable. It's not a taxonomy.
52
  *
53
  * @return bool
54
  */
55
  public function is_term_meta_capable() {
56
+ return $this->is_category() || $this->is_tag() || $this->is_tax();
57
+ }
58
+
59
+ /**
60
+ * Returns the term meta item by key.
61
+ *
62
+ * @param string $item The item to get.
63
+ * @param int $term_id The Term ID.
64
+ * @param bool $use_cache Whether to use caching; only has effect when $term_id is set.
65
+ * @return mixed The term meta item. Null when not found.
66
+ */
67
+ public function get_term_meta_item( $item, $term_id = 0, $use_cache = true ) {
68
+
69
+ if ( ! $term_id ) {
70
+ $meta = $this->get_current_term_meta();
71
+ } else {
72
+ $meta = $this->get_term_meta( $term_id, $use_cache );
73
+ }
74
+
75
+ return isset( $meta[ $item ] ) ? $meta[ $item ] : null;
76
  }
77
 
78
  /**
79
  * Returns and caches term meta for the current query.
80
  *
81
  * @since 3.0.0
82
+ * @since 4.0.1 Now uses the filterable `get_the_real_ID()`
83
  * @staticvar array $cache
84
  *
85
  * @return array The current term meta.
92
  return $cache;
93
 
94
  if ( $this->is_term_meta_capable() ) {
95
+ $cache = $this->get_term_meta( $this->get_the_real_ID() ) ?: [];
96
  } else {
97
  $cache = [];
98
  }
108
  * @since 2.8.0 Added filter.
109
  * @since 3.0.0 Added filter.
110
  * @since 3.1.0 Deprecated filter.
111
+ * @since 4.0.0 1. Removed deprecated filter.
112
+ * 2. Now fills in defaults.
113
  * @staticvar array $cache
114
  *
115
  * @param int $term_id The Term ID.
123
 
124
  if ( isset( $cache[ $term_id ] ) )
125
  return $cache[ $term_id ];
 
 
126
  }
127
 
128
+ /**
129
+ * We can't trust the filter to always contain the expected keys.
130
+ * However, it may contain more keys than we anticipated. Merge them.
131
+ */
132
+ $defaults = array_merge(
133
+ $this->get_unfiltered_term_meta_defaults(),
134
+ $this->get_term_meta_defaults( $term_id )
135
+ );
136
 
137
+ $meta = \get_term_meta( $term_id, THE_SEO_FRAMEWORK_TERM_OPTIONS, true ) ?: [];
138
+
139
+ static $has_deprecated_filter = null;
140
+ if ( null === $has_deprecated_filter && \has_filter( 'the_seo_framework_current_term_meta' ) ) {
141
+ $has_deprecated_filter = true;
142
+ $this->_deprecated_filter( 'the_seo_framework_current_term_meta', '4.0.0', 'get_term_metadata' );
143
+ }
144
+
145
+ if ( $has_deprecated_filter && $meta ) {
146
  /**
147
  * @since 3.0.0
148
+ * @since 4.0.0 Deprecated.
149
+ * @deprecated
150
+ * @param array $meta The CURRENT term metadata.
151
  * @param int $term_id The term ID.
152
  */
153
+ $meta = \apply_filters( 'the_seo_framework_current_term_meta', $meta, $term_id );
 
154
 
155
+ /**
156
+ * Filter the extraneous term meta items based on defaults' keys.
157
+ * This is redundant, but in line with the requirement at `get_post_meta()`
158
+ * where we get all metadata without a key.
159
+ *
160
+ * @see `$this->s_term_meta()`, which strips them out, already. As such,
161
+ * we only use this when the (deprecated) filter is used.
162
+ */
163
+ $meta = array_intersect_key(
164
+ $meta,
165
+ $defaults
166
+ );
167
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
+ return $cache[ $term_id ] = array_merge( $defaults, $meta );
170
  }
171
 
172
  /**
174
  *
175
  * @since 2.7.0
176
  * @since 3.1.0 This is now always used.
177
+ * @since 4.0.0 : 1. Added $term_id parameter.
178
+ * 2. Added 'redirect' value.
179
+ * 3. Added 'title_no_blog_name' value.
180
+ * 4. Removed 'saved_flag' value.
181
  *
182
+ * @param int $term_id The term ID.
183
  * @return array The Term Metadata default options.
184
  */
185
+ public function get_term_meta_defaults( $term_id = 0 ) {
186
  /**
187
  * @since 2.1.8
188
  * @param array $defaults
189
+ * @param int $term_id The current term ID.
190
  */
191
+ return (array) \apply_filters_ref_array(
192
+ 'the_seo_framework_term_meta_defaults',
193
+ [
194
+ $this->get_unfiltered_term_meta_defaults(),
195
+ $term_id ?: $this->get_the_real_ID(),
196
+ ]
197
+ );
198
+ }
199
+
200
+ /**
201
+ * Returns the unfiltered term meta defaults.
202
+ *
203
+ * @since 4.0.0
204
+ *
205
+ * @return array The default, unfiltered, post meta.
206
+ */
207
+ protected function get_unfiltered_term_meta_defaults() {
208
+ return [
209
+ 'doctitle' => '',
210
+ 'title_no_blog_name' => 0,
211
+ 'description' => '',
212
+ 'og_title' => '',
213
+ 'og_description' => '',
214
+ 'tw_title' => '',
215
+ 'tw_description' => '',
216
+ 'social_image_url' => '',
217
+ 'social_image_id' => 0,
218
+ 'canonical' => '',
219
+ 'noindex' => 0,
220
+ 'nofollow' => 0,
221
+ 'noarchive' => 0,
222
+ 'redirect' => '',
223
+ ];
224
  }
225
 
226
  /**
227
  * Sanitizes and saves term meta data when a term is altered.
228
  *
229
  * @since 2.7.0
230
+ * @since 4.0.0: 1. Renamed from `update_term_meta`
231
+ * 2. noindex, nofollow, noarchive are now converted to qubits.
232
+ * 3. Added new keys to sanitize.
233
+ * 4. Now marked as private.
234
+ * 5. Added more sanity protection.
235
+ * 6. No longer runs when no `autodescription-meta` POST data is sent.
236
+ * 7. Now uses the current term meta to set new values.
237
+ * 8. No longer deletes meta from abstracting plugins on save when they're deactivated.
238
+ * 9. Now allows updating during `WP_AJAX`.
239
  * @securitycheck 3.0.0 OK.
240
+ * @access private
241
+ * Use $this->save_term_meta() instead.
242
  *
243
+ * @param int $term_id Term ID.
244
+ * @param int $tt_id Term taxonomy ID.
245
+ * @param string $taxonomy Taxonomy slug.
 
246
  */
247
+ public function _update_term_meta( $term_id, $tt_id, $taxonomy ) {
248
+ // phpcs:disable, WordPress.Security.NonceVerification
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
 
250
+ if ( ! empty( $_POST['autodescription-quick'] ) ) {
251
+ $this->update_quick_edit_term_meta( $term_id, $tt_id, $taxonomy );
252
+ } elseif ( ! empty( $_POST['autodescription-meta'] ) ) {
253
+ $this->update_term_edit_term_meta( $term_id, $tt_id, $taxonomy );
254
+ }
255
+
256
+ // phpcs:enable, WordPress.Security.NonceVerification
257
+ }
258
+
259
+ /**
260
+ * Overwrites all of the term meta on term-edit.
261
+ *
262
+ * @since 4.0.0
263
+ * @since 4.0.2 1: Now tests for valid term ID in the term object.
264
+ * 2: Now continues using the filtered term object.
265
+ *
266
+ * @param int $term_id Term ID.
267
+ * @param int $tt_id Term taxonomy ID.
268
+ * @param string $taxonomy Taxonomy slug.
269
+ * @return void
270
+ */
271
+ protected function update_term_edit_term_meta( $term_id, $tt_id, $taxonomy ) {
272
+
273
+ $term = \get_term( $term_id, $taxonomy );
274
+
275
+ // We could test for is_wp_error( $term ), but this is more to the point.
276
+ if ( empty( $term->term_id ) ) return;
277
+
278
+ //* Check again against ambiguous injection...
279
+ // Note, however: function wp_update_term() already performs all these checks for us before firing this callback's action.
280
+ if ( ! \current_user_can( 'edit_term', $term->term_id ) ) return;
281
+ if ( ! isset( $_POST['_wpnonce'] ) ) return;
282
+ if ( ! \wp_verify_nonce( \stripslashes_from_strings_only( $_POST['_wpnonce'] ), 'update-tag_' . $term->term_id ) ) return;
283
+
284
+ $data = (array) $_POST['autodescription-meta'];
285
+
286
+ $this->save_term_meta( $term->term_id, $tt_id, $taxonomy, $data );
287
+ }
288
+
289
+ /**
290
+ * Overwrites a part of the term meta on quick-edit.
291
+ *
292
+ * @since 4.0.0
293
+ * @since 4.0.2 1: Now tests for valid term ID in the term object.
294
+ * 2: Now continues using the filtered term object.
295
+ *
296
+ * @param int $term_id Term ID.
297
+ * @param int $tt_id Term taxonomy ID.
298
+ * @param string $taxonomy Taxonomy slug.
299
+ * @return void
300
+ */
301
+ protected function update_quick_edit_term_meta( $term_id, $tt_id, $taxonomy ) {
302
+
303
+ $term = \get_term( $term_id, $taxonomy );
304
+
305
+ // We could test for is_wp_error( $term ), but this is more to the point.
306
+ if ( empty( $term->term_id ) ) return;
307
+
308
+ //* Check again against ambiguous injection...
309
+ // Note, however: function wp_ajax_inline_save_tax() already performs all these checks for us before firing this callback's action.
310
+ if ( ! \current_user_can( 'edit_term', $term->term_id ) ) return;
311
+ if ( ! \check_ajax_referer( 'taxinlineeditnonce', '_inline_edit', false ) ) return;
312
+
313
+ // Unlike the term-edit saving, we don't reset the data, just overwrite what's given.
314
+ // This is because we only update a portion of the meta.
315
+ $data = array_merge(
316
+ $this->get_term_meta( $term->term_id, false ),
317
+ (array) $_POST['autodescription-quick']
318
+ );
319
+
320
+ $this->save_term_meta( $term->term_id, $tt_id, $taxonomy, $data );
321
+ }
322
+
323
+ /**
324
+ * Updates single term meta value.
325
+ *
326
+ * Note that this method can be more resource intensive than you intend it to be,
327
+ * as it reprocesses all term meta.
328
+ *
329
+ * @since 4.0.0
330
+ * @since 4.0.2 1: Now tests for valid term ID in the term object.
331
+ * 2: Now continues using the filtered term object.
332
+ * @uses $this->save_term_meta() to process all data.
333
+ *
334
+ * @param string $item The item to update.
335
+ * @param mixed $value The value the item should be at.
336
+ * @param int $term_id Term ID.
337
+ * @param int $tt_id Term taxonomy ID.
338
+ * @param string $taxonomy Taxonomy slug.
339
+ */
340
+ public function update_single_term_meta_item( $item, $value, $term_id, $tt_id, $taxonomy ) {
341
+
342
+ $term = \get_term( $term_id, $taxonomy );
343
+
344
+ // We could test for is_wp_error( $term ), but this is more to the point.
345
+ if ( empty( $term->term_id ) ) return;
346
+
347
+ $meta = $this->get_term_meta( $term->term_id, false );
348
+ $meta[ $item ] = $value;
349
+
350
+ $this->save_term_meta( $term->term_id, $tt_id, $taxonomy, $meta );
351
+ }
352
+
353
+ /**
354
+ * Updates term meta from input.
355
+ *
356
+ * @since 4.0.0
357
+ * @since 4.0.2 1: Now tests for valid term ID in the term object.
358
+ * 2: Now continues using the filtered term object.
359
+ *
360
+ * @param int $term_id Term ID.
361
+ * @param int $tt_id Term Taxonomy ID.
362
+ * @param string $taxonomy Taxonomy slug.
363
+ * @param array $data The data to save.
364
+ */
365
+ public function save_term_meta( $term_id, $tt_id, $taxonomy, array $data ) {
366
+
367
+ $term = \get_term( $term_id, $taxonomy );
368
+
369
+ // We could test for is_wp_error( $term ), but this is more to the point.
370
+ if ( empty( $term->term_id ) ) return;
371
+
372
+ $data = (array) \wp_parse_args( $data, $this->get_term_meta_defaults( $term->term_id ) );
373
+ $data = $this->s_term_meta( $data );
374
+
375
+ /**
376
+ * @since 3.1.0
377
+ * @param array $data The data that's going to be saved.
378
+ * @param int $term_id The term ID.
379
+ * @param int $tt_id The term taxonomy ID.
380
+ * @param string $taxonomy The taxonomy slug.
381
+ */
382
+ $data = (array) \apply_filters_ref_array(
383
+ 'the_seo_framework_save_term_data',
384
+ [
385
  $data,
386
+ $term->term_id,
387
  $tt_id,
388
  $taxonomy,
389
+ ]
390
+ );
391
 
392
+ \update_term_meta( $term->term_id, THE_SEO_FRAMEWORK_TERM_OPTIONS, $data );
 
393
  }
394
 
395
  /**
396
  * Delete term meta data when a term is deleted.
397
+ * Deletes only the default data keys; or everything when only that is present.
398
+ *
399
+ * @since 4.0.0
400
+ * @access private
401
+ *
402
+ * @param int $term_id Term ID.
403
+ * @param int $tt_id Term Taxonomy ID.
404
+ * @param string $taxonomy Taxonomy slug.
405
+ */
406
+ public function _delete_term_meta( $term_id, $tt_id, $taxonomy ) { // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
407
+ $this->delete_term_meta( $term_id );
408
+ }
409
+
410
+ /**
411
+ * Deletes term meta.
412
+ * Deletes only the default data keys; or everything when only that is present.
413
  *
414
  * @since 2.7.0
415
+ * @since 4.0.0 Removed 2nd, unused, parameter.
416
  *
417
  * @param int $term_id Term ID.
 
418
  */
419
+ public function delete_term_meta( $term_id ) {
420
 
421
  //* If this results in an empty data string, all data has already been removed by WP core.
422
  $data = \get_term_meta( $term_id, THE_SEO_FRAMEWORK_TERM_OPTIONS, true );
423
 
424
  if ( is_array( $data ) ) {
425
+ foreach ( $this->get_term_meta_defaults( $term_id ) as $key => $value ) {
426
  unset( $data[ $key ] );
427
  }
428
  }
434
  }
435
  }
436
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  /**
438
  * Returns the taxonomy type object label. Either plural or singular.
439
  *
440
  * @since 3.1.0
441
  * @see $this->get_post_type_label() For the singular alternative.
442
  *
443
+ * @param string $tax_type The taxonomy type. Required.
444
+ * @param bool $singular Wether to get the singlural or plural name.
445
  * @return string The Taxonomy Type name/label, if found.
446
  */
447
  public function get_tax_type_label( $tax_type, $singular = true ) {
471
  return [];
472
 
473
  $taxonomies = \get_object_taxonomies( $post_type, 'objects' );
474
+ $taxonomies = array_filter(
475
+ $taxonomies,
476
+ function( $t ) {
477
+ return $t->hierarchical;
478
+ }
479
+ );
480
 
481
  switch ( $get ) {
482
  case 'names':
inc/classes/user-data.class.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Classes
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
@@ -68,10 +70,6 @@ class User_Data extends Term_Data {
68
  $post = \get_post( $this->get_the_real_ID() );
69
  $cache = isset( $post->post_author ) ? (int) $post->post_author : 0;
70
  }
71
- // This works... but the function name needs to be rewritten; Also, the related meta yields no social "SEO" value.
72
- // elseif ( $this->is_author() ) {
73
- // $cache = $this->get_the_real_ID();
74
- // }
75
 
76
  return $cache ?: $cache = 0;
77
  }
@@ -82,7 +80,7 @@ class User_Data extends Term_Data {
82
  *
83
  * @since 2.7.0
84
  *
85
- * @return int $user_id : 0 if user is not found.
86
  */
87
  public function get_user_id() {
88
 
@@ -103,9 +101,9 @@ class User_Data extends Term_Data {
103
  * @since 2.8.0 Always returns array, even if no value is assigned.
104
  * @staticvar array $usermeta_cache
105
  *
106
- * @param int $user_id The user ID.
107
- * @param string $key The user metadata key. Leave empty to fetch all data.
108
- * @param bool $use_cache Whether to store and use options from cache.
109
  * @return array The user SEO meta data.
110
  */
111
  public function get_user_meta( $user_id, $key = THE_SEO_FRAMEWORK_USER_OPTIONS, $use_cache = true ) {
@@ -126,9 +124,9 @@ class User_Data extends Term_Data {
126
  *
127
  * @since 3.0.0
128
  *
129
- * @param int $author_id The author ID. When empty, it will return $default.
130
- * @param string $option The option name. When empty, it will return $default.
131
- * @param mixed $default The default value to return when the data doesn't exist.
132
  * @return mixed The metadata value
133
  */
134
  public function get_author_option( $author_id, $option, $default = null ) {
@@ -144,8 +142,8 @@ class User_Data extends Term_Data {
144
  *
145
  * @since 3.0.0
146
  *
147
- * @param string $option The option name.
148
- * @param mixed $default The default value to return when the data doesn't exist.
149
  * @return mixed The metadata value
150
  */
151
  public function get_current_author_option( $option, $default = null ) {
@@ -165,9 +163,9 @@ class User_Data extends Term_Data {
165
  * @staticvar array $options_cache
166
  * @staticvar array $notfound_cache
167
  *
168
- * @param int $user_id The user ID. When empty, it will try to fetch the current user.
169
- * @param string $option The option name.
170
- * @param mixed $default The default value to return when the data doesn't exist.
171
  * @return mixed The metadata value.
172
  */
173
  public function get_user_option( $user_id = 0, $option, $default = null ) {
@@ -206,9 +204,9 @@ class User_Data extends Term_Data {
206
  * @since 2.7.0
207
  * @since 2.8.0 New users now get a new array assigned.
208
  *
209
- * @param int $user_id The user ID.
210
- * @param string $option The user's SEO metadata option.
211
- * @param mixed $value The escaped option value.
212
  * @return bool True on success. False on failure.
213
  */
214
  public function update_user_option( $user_id = 0, $option, $value ) {
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Classes\Facade\User_Data
4
+ * @subpackage The_SEO_Framework\Data
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
70
  $post = \get_post( $this->get_the_real_ID() );
71
  $cache = isset( $post->post_author ) ? (int) $post->post_author : 0;
72
  }
 
 
 
 
73
 
74
  return $cache ?: $cache = 0;
75
  }
80
  *
81
  * @since 2.7.0
82
  *
83
+ * @return int The user ID. 0 if user is not found.
84
  */
85
  public function get_user_id() {
86
 
101
  * @since 2.8.0 Always returns array, even if no value is assigned.
102
  * @staticvar array $usermeta_cache
103
  *
104
+ * @param int $user_id The user ID.
105
+ * @param string $key The user metadata key. Leave empty to fetch all data.
106
+ * @param bool $use_cache Whether to store and use options from cache.
107
  * @return array The user SEO meta data.
108
  */
109
  public function get_user_meta( $user_id, $key = THE_SEO_FRAMEWORK_USER_OPTIONS, $use_cache = true ) {
124
  *
125
  * @since 3.0.0
126
  *
127
+ * @param int $author_id The author ID. When empty, it will return $default.
128
+ * @param string $option The option name. When empty, it will return $default.
129
+ * @param mixed $default The default value to return when the data doesn't exist.
130
  * @return mixed The metadata value
131
  */
132
  public function get_author_option( $author_id, $option, $default = null ) {
142
  *
143
  * @since 3.0.0
144
  *
145
+ * @param string $option The option name.
146
+ * @param mixed $default The default value to return when the data doesn't exist.
147
  * @return mixed The metadata value
148
  */
149
  public function get_current_author_option( $option, $default = null ) {
163
  * @staticvar array $options_cache
164
  * @staticvar array $notfound_cache
165
  *
166
+ * @param int $user_id The user ID. When empty, it will try to fetch the current user.
167
+ * @param string $option The option name.
168
+ * @param mixed $default The default value to return when the data doesn't exist.
169
  * @return mixed The metadata value.
170
  */
171
  public function get_user_option( $user_id = 0, $option, $default = null ) {
204
  * @since 2.7.0
205
  * @since 2.8.0 New users now get a new array assigned.
206
  *
207
+ * @param int $user_id The user ID.
208
+ * @param string $option The user's SEO metadata option.
209
+ * @param mixed $value The escaped option value.
210
  * @return bool True on success. False on failure.
211
  */
212
  public function update_user_option( $user_id = 0, $option, $value ) {
inc/compat/index.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Where every something, being blent together,
4
+ * Turns to a wild of nothing, save of joy,
5
+ * Express’d and not express’d. But when this ring
6
+ * Parts from this finger, then parts life from hence:
7
+ * O! then be bold to say Bassanio’s dead.
8
+ *
9
+ * - William Shakespeare, The Merchant of Venice
10
+ */
inc/compat/php-mbstring.php CHANGED
@@ -1,6 +1,9 @@
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\PHP\mbstring
 
 
 
4
  */
5
 
6
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\PHP\mbstring
4
+ * @subpackage The_SEO_Framework\Compatibility
5
+ *
6
+ * @ignore this file isn't loaded.
7
  */
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
inc/compat/plugin-bbpress.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\Plugin\bbPress
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
@@ -43,17 +45,19 @@ function _bbpress_filter_order_keys( $current_keys = [] ) {
43
  * 2. Now no longer fixes the title when `is_tax()` is true. Because,
44
  * this method is no longer necessary when bbPress fixes this issue.
45
  * This should be fixed as of bbPress 2.6. Which seemed to be released internally August 6th, 2018.
 
46
  * @access private
47
  *
48
- * @param string $title The filter title.
49
- * @param array $args The title arguments.
 
50
  * @return string $title The bbPress title.
51
  */
52
- function _bbpress_filter_pre_title( $title = '', $args = [], $escape = true ) {
53
 
54
- if ( \is_bbpress() ) {
55
- if ( \bbp_is_topic_tag() && ! \the_seo_framework()->is_tax() ) {
56
- $term = \get_queried_object();
57
  $title = isset( $term->name ) ? $term->name : \the_seo_framework()->get_static_untitled_title();
58
  }
59
  }
@@ -61,28 +65,32 @@ function _bbpress_filter_pre_title( $title = '', $args = [], $escape = true ) {
61
  return $title;
62
  }
63
 
64
- \add_filter( 'the_seo_framework_fetched_description_excerpt', __NAMESPACE__ . '\\_bbpress_filter_excerpt_generation', 10 );
65
  /**
66
  * Fixes bbPress excerpts.
67
  *
68
- * bbPress has a hard time maintaining WordPress' query after the original query.
69
- * Reasons unknown.
70
  * This function fixes the Excerpt part.
71
  *
72
  * @since 2.9.0
73
  * @since 3.0.4 : Default value for $max_char_length has been increased from 155 to 300.
74
  * @since 3.1.0 Now no longer fixes the description when `is_tax()` is true.
75
  * @see `_bbpress_filter_pre_title()` for explanation.
 
76
  * @access private
77
  *
78
- * @param string $excerpt The excerpt to use.
 
 
 
79
  * @return string The excerpt.
80
  */
81
- function _bbpress_filter_excerpt_generation( $excerpt = '' ) {
82
 
83
- if ( \is_bbpress() ) {
84
- if ( \bbp_is_topic_tag() && ! \the_seo_framework()->is_tax() ) {
85
- $term = \get_queried_object();
86
  $description = $term->description ?: '';
87
 
88
  //* Always overwrite, even when none is found.
@@ -93,42 +101,45 @@ function _bbpress_filter_excerpt_generation( $excerpt = '' ) {
93
  return $excerpt;
94
  }
95
 
96
- \add_filter( 'the_seo_framework_custom_field_description', __NAMESPACE__ . '\\_bbpress_filter_custom_field_description' );
97
  /**
98
  * Fixes bbPress custom Description for social meta.
99
  *
100
- * bbPress has a hard time maintaining WordPress' query after the original query.
101
- * Reasons unknown.
102
  * This function fixes the Custom Description part.
103
  *
104
  * @since 2.9.0
 
105
  * @access private
106
  *
107
- * @param string $description The description.
 
 
108
  * @return string The custom description.
109
  */
110
- function _bbpress_filter_custom_field_description( $description = '' ) {
111
 
112
- if ( \is_bbpress() ) {
113
  if ( \bbp_is_topic_tag() ) {
114
  $data = \the_seo_framework()->get_term_meta( \get_queried_object_id() );
115
  if ( ! empty( $data['description'] ) ) {
116
- $description = $data['description'];
117
  } else {
118
- $description = '';
119
  }
120
  }
121
  }
122
 
123
- return $description;
124
  }
125
 
126
  \add_filter( 'the_seo_framework_do_adjust_archive_query', __NAMESPACE__ . '\\_bbpress_filter_do_adjust_query', 10, 2 );
127
  /**
128
  * Fixes bbPress exclusion of first reply.
129
  *
130
- * bbPress has a hard time maintaining WordPress' query after the original query.
131
- * Reasons unknown.
132
  * This function fixes the query alteration part.
133
  *
134
  * @since 3.0.3
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\Plugin\bbPress
4
+ * @subpackage The_SEO_Framework\Compatibility
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
45
  * 2. Now no longer fixes the title when `is_tax()` is true. Because,
46
  * this method is no longer necessary when bbPress fixes this issue.
47
  * This should be fixed as of bbPress 2.6. Which seemed to be released internally August 6th, 2018.
48
+ * @since 4.0.0 No longer overrules external queries.
49
  * @access private
50
  *
51
+ * @param string $title The filter title.
52
+ * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
53
+ * Is null when query is autodetermined.
54
  * @return string $title The bbPress title.
55
  */
56
+ function _bbpress_filter_pre_title( $title = '', $args = null ) {
57
 
58
+ if ( null === $args && \is_bbpress() ) {
59
+ if ( \bbp_is_topic_tag() ) {
60
+ $term = \get_queried_object();
61
  $title = isset( $term->name ) ? $term->name : \the_seo_framework()->get_static_untitled_title();
62
  }
63
  }
65
  return $title;
66
  }
67
 
68
+ \add_filter( 'the_seo_framework_fetched_description_excerpt', __NAMESPACE__ . '\\_bbpress_filter_excerpt_generation', 10, 3 );
69
  /**
70
  * Fixes bbPress excerpts.
71
  *
72
+ * Now, bbPress has a hard time maintaining WordPress' query after the original query.
73
+ * This should be fixed with bbPress 3.0.
74
  * This function fixes the Excerpt part.
75
  *
76
  * @since 2.9.0
77
  * @since 3.0.4 : Default value for $max_char_length has been increased from 155 to 300.
78
  * @since 3.1.0 Now no longer fixes the description when `is_tax()` is true.
79
  * @see `_bbpress_filter_pre_title()` for explanation.
80
+ * @since 4.0.0 No longer overrules external queries.
81
  * @access private
82
  *
83
+ * @param string $excerpt The excerpt to use.
84
+ * @param int $page_id Deprecated.
85
+ * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
86
+ * Is null when query is autodetermined.
87
  * @return string The excerpt.
88
  */
89
+ function _bbpress_filter_excerpt_generation( $excerpt = '', $page_id = 0, $args = null ) {
90
 
91
+ if ( null === $args && \is_bbpress() ) {
92
+ if ( \bbp_is_topic_tag() ) {
93
+ $term = \get_queried_object();
94
  $description = $term->description ?: '';
95
 
96
  //* Always overwrite, even when none is found.
101
  return $excerpt;
102
  }
103
 
104
+ \add_filter( 'the_seo_framework_custom_field_description', __NAMESPACE__ . '\\_bbpress_filter_custom_field_description', 10, 2 );
105
  /**
106
  * Fixes bbPress custom Description for social meta.
107
  *
108
+ * Now, bbPress has a hard time maintaining WordPress' query after the original query.
109
+ * This should be fixed with bbPress 3.0.
110
  * This function fixes the Custom Description part.
111
  *
112
  * @since 2.9.0
113
+ * @since 4.0.0 No longer overrules external queries.
114
  * @access private
115
  *
116
+ * @param string $desc The custom-field description.
117
+ * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
118
+ * Is null when query is autodetermined.
119
  * @return string The custom description.
120
  */
121
+ function _bbpress_filter_custom_field_description( $desc = '', $args = null ) {
122
 
123
+ if ( null === $args && \is_bbpress() ) {
124
  if ( \bbp_is_topic_tag() ) {
125
  $data = \the_seo_framework()->get_term_meta( \get_queried_object_id() );
126
  if ( ! empty( $data['description'] ) ) {
127
+ $desc = $data['description'];
128
  } else {
129
+ $desc = '';
130
  }
131
  }
132
  }
133
 
134
+ return $desc;
135
  }
136
 
137
  \add_filter( 'the_seo_framework_do_adjust_archive_query', __NAMESPACE__ . '\\_bbpress_filter_do_adjust_query', 10, 2 );
138
  /**
139
  * Fixes bbPress exclusion of first reply.
140
  *
141
+ * Now, bbPress has a hard time maintaining WordPress' query after the original query.
142
+ * This should be fixed with bbPress 3.0.
143
  * This function fixes the query alteration part.
144
  *
145
  * @since 3.0.3
inc/compat/plugin-buddypress.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\Plugin\BuddyPress
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\Plugin\BuddyPress
4
+ * @subpackage The_SEO_Framework\Compatibility
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
inc/compat/plugin-polylang.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Compat\Plugin\Polylang
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
@@ -23,7 +25,7 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() a
23
  *
24
  * @since 3.1.0
25
  *
26
- * @param $string The title or description
27
  * @return string
28
  */
29
  function pll__( $string ) {
@@ -43,7 +45,7 @@ function pll__( $string ) {
43
  *
44
  * @since 3.2.4
45
  *
46
- * @param array $whitelist
47
  * @return array
48
  */
49
  function _whitelist_tsf_urls( $whitelist ) {
@@ -58,14 +60,40 @@ function _whitelist_tsf_urls( $whitelist ) {
58
  *
59
  * @since 3.2.4
60
  *
61
- * @param array $blacklist
62
  * @return array
63
  */
64
  function _blaclist_tsf_sitemap_styles( $blacklist ) {
65
- $blacklist[] = [ 'function' => 'get_sitemap_xsl_url' ];
 
 
 
66
  return $blacklist;
67
  }
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  \add_filter( 'the_seo_framework_rel_canonical_output', __NAMESPACE__ . '\\_fix_home_url', 10, 2 );
70
  \add_filter( 'the_seo_framework_ogurl_output', __NAMESPACE__ . '\\_fix_home_url', 10, 2 );
71
  /**
@@ -73,6 +101,8 @@ function _blaclist_tsf_sitemap_styles( $blacklist ) {
73
  * This fixes user_trailingslashit() issues.
74
  *
75
  * @since 3.2.4
 
 
76
  */
77
  function _fix_home_url( $url, $id ) {
78
  return \the_seo_framework()->is_front_page_by_ID( $id ) && \get_option( 'permalink_structure' ) ? \trailingslashit( $url ) : $url;
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Compat\Plugin\PolyLang
4
+ * @subpackage The_SEO_Framework\Compatibility
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
25
  *
26
  * @since 3.1.0
27
  *
28
+ * @param string $string The title or description
29
  * @return string
30
  */
31
  function pll__( $string ) {
45
  *
46
  * @since 3.2.4
47
  *
48
+ * @param array $whitelist The wildcard file parts that are whitelisted.
49
  * @return array
50
  */
51
  function _whitelist_tsf_urls( $whitelist ) {
60
  *
61
  * @since 3.2.4
62
  *
63
+ * @param array $blacklist The wildcard file parts that are blacklisted.
64
  * @return array
65
  */
66
  function _blaclist_tsf_sitemap_styles( $blacklist ) {
67
+ // y u no recurse
68
+ $blacklist[] = [ 'function' => 'get_expected_sitemap_endpoint_url' ];
69
+ $blacklist[] = [ 'function' => 'get_sitemap_base_path_info' ];
70
+ $blacklist[] = [ 'file' => 'autodescription/inc/compat/plugin-polylang.php' ];
71
  return $blacklist;
72
  }
73
 
74
+ \add_filter( 'the_seo_framework_sitemap_path_prefix', __NAMESPACE__ . '\\_fix_sitemap_prefix', 9 );
75
+ /**
76
+ * Fixes the sitemap prefix, because setting the home URL globally requires only one filter.
77
+ *
78
+ * @since 4.0.0
79
+ * @param string $prefix The path prefix. Ideally appended with a slash.
80
+ * Recommended return value: "$prefix$custompath/"
81
+ * @return string New prefix.
82
+ */
83
+ function _fix_sitemap_prefix( $prefix = '' ) {
84
+
85
+ if ( function_exists( '\\pll_home_url' ) ) {
86
+ $home_url = \home_url();
87
+ $ruined_home_url = \pll_home_url();
88
+
89
+ $path = trim( substr_replace( $ruined_home_url, '', 0, strlen( $home_url ) ), '/' );
90
+
91
+ return $path ? "$prefix$path/" : $prefix;
92
+ }
93
+
94
+ return $prefix;
95
+ }
96
+
97
  \add_filter( 'the_seo_framework_rel_canonical_output', __NAMESPACE__ . '\\_fix_home_url', 10, 2 );
98
  \add_filter( 'the_seo_framework_ogurl_output', __NAMESPACE__ . '\\_fix_home_url', 10, 2 );
99
  /**
101
  * This fixes user_trailingslashit() issues.
102
  *
103
  * @since 3.2.4
104
+ * @param string $url The url to fix.
105
+ * @param int $id The page or term ID.
106
  */
107
  function _fix_home_url( $url, $id ) {
108
  return \the_seo_framework()->is_front_page_by_ID( $id ) && \get_option( 'permalink_structure' ) ? \trailingslashit( $url ) : $url;
inc/compat/plugin-ultimatemember.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\Plugin\UltimateMember
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
@@ -12,19 +14,22 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() a
12
  */
13
  \remove_action( 'wp_head', 'um_profile_dynamic_meta_desc', 9999999 );
14
 
15
- \add_filter( 'the_seo_framework_title_from_generation', __NAMESPACE__ . '\\_um_filter_generated_title', 10, 1 );
16
  /**
17
  * Filters the custom title for UM.
18
  *
19
  * @since 3.1.0
 
20
  * @access private
21
  *
22
- * @param string $title The current title.
 
 
23
  * @return string The filtered title.
24
  */
25
- function _um_filter_generated_title( $title = '' ) {
26
 
27
- if ( \the_seo_framework()->can_i_use( [
28
  'functions' => [
29
  'um_is_core_page',
30
  'um_get_requested_user',
@@ -36,7 +41,7 @@ function _um_filter_generated_title( $title = '' ) {
36
  ] ) ) {
37
  if ( \um_is_core_page( 'user' ) && \um_get_requested_user() ) {
38
  \um_fetch_user( \um_get_requested_user() );
39
- $user_id = um_user( 'ID' );
40
  \um_reset_user();
41
 
42
  $title = \um_get_display_name( $user_id );
@@ -70,7 +75,7 @@ function _um_filter_generated_url( $url = '' ) {
70
  ] ) ) {
71
  if ( \um_is_core_page( 'user' ) && \um_get_requested_user() ) {
72
  \um_fetch_user( \um_get_requested_user() );
73
- $url = um_user_profile_url();
74
  \um_reset_user();
75
  }
76
  }
@@ -78,19 +83,22 @@ function _um_filter_generated_url( $url = '' ) {
78
  return $url;
79
  }
80
 
81
- \add_filter( 'the_seo_framework_generated_description', __NAMESPACE__ . '\\_um_filter_generated_description', 10, 1 );
82
  /**
83
  * Filters the generated description for UM.
84
  *
85
  * @since 3.1.0
 
86
  * @access private
87
  *
88
- * @param string $url The current description.
 
 
89
  * @return string The filtered description.
90
  */
91
- function _um_filter_generated_description( $description = '' ) {
92
 
93
- if ( \the_seo_framework()->can_i_use( [
94
  'functions' => [
95
  'um_is_core_page',
96
  'um_get_requested_user',
@@ -110,10 +118,10 @@ function _um_filter_generated_description( $description = '' ) {
110
  } catch ( \Exception $e ) {
111
  $_description = '';
112
  }
113
- $description = $_description ?: $description;
114
  \um_reset_user();
115
  }
116
  }
117
 
118
- return $description;
119
  }
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\Plugin\UltimateMember
4
+ * @subpackage The_SEO_Framework\Compatibility
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
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',
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 );
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
  }
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',
118
  } catch ( \Exception $e ) {
119
  $_description = '';
120
  }
121
+ $desc = $_description ?: $desc;
122
  \um_reset_user();
123
  }
124
  }
125
 
126
+ return $desc;
127
  }
inc/compat/plugin-woocommerce.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\Plugin\WooCommerce
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
@@ -13,15 +15,142 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() a
13
  * Initializes WooCommerce compatibility.
14
  *
15
  * @since 3.1.0
 
16
  * @uses \is_product()
17
  */
18
  function _init_wc_compat() {
19
- \add_action( 'the_seo_framework_do_before_output', function() {
20
- /**
21
- * Removes TSF breadcrumbs.
22
- */
23
- if ( function_exists( '\\is_product' ) && \is_product() ) {
24
- \add_filter( 'the_seo_framework_json_breadcrumb_output', '__return_false' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
- } );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  }
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\Plugin\WooCommerce
4
+ * @subpackage The_SEO_Framework\Compatibility
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
15
  * Initializes WooCommerce compatibility.
16
  *
17
  * @since 3.1.0
18
+ * @access private
19
  * @uses \is_product()
20
  */
21
  function _init_wc_compat() {
22
+ \add_action(
23
+ 'the_seo_framework_do_before_output',
24
+ function() {
25
+ /**
26
+ * Removes TSF breadcrumbs. WooCommerce outputs theirs.
27
+ */
28
+ if ( function_exists( '\\is_product' ) && \is_product() ) {
29
+ \add_filter( 'the_seo_framework_json_breadcrumb_output', '__return_false' );
30
+ }
31
+ }
32
+ );
33
+ }
34
+
35
+ \add_filter( 'the_seo_framework_image_generation_params', __NAMESPACE__ . '\\_adjust_image_generation_params', 10, 2 );
36
+ /**
37
+ * Adjusts image generation parameters.
38
+ *
39
+ * @since 4.0.0
40
+ * @access private
41
+ *
42
+ * @param array $params : [
43
+ * string size: The image size to use.
44
+ * boolean multi: Whether to allow multiple images to be returned.
45
+ * array cbs: The callbacks to parse. Ideally be generators, so we can halt remotely.
46
+ * array fallback: The callbacks to parse. Ideally be generators, so we can halt remotely.
47
+ * ];
48
+ * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
49
+ * Is null when query is autodetermined.
50
+ * @return array $params
51
+ */
52
+ function _adjust_image_generation_params( $params, $args ) {
53
+
54
+ $is_product = false;
55
+ $is_product_category = false;
56
+
57
+ if ( null === $args ) {
58
+ $is_product = \the_seo_framework()->is_wc_product();
59
+ $is_product_category = function_exists( '\\is_product_category' ) && \is_product_category();
60
+ } else {
61
+ if ( $args['taxonomy'] ) {
62
+ if ( function_exists( '\\is_product_category' ) ) {
63
+ $term = \get_term( $args['id'], $args['taxonomy'] );
64
+ $is_product_category = $term && \is_product_category( $term );
65
+ }
66
+ } else {
67
+ $is_product = \the_seo_framework()->is_wc_product( $args['id'] );
68
+ }
69
+ }
70
+
71
+ if ( $is_product ) {
72
+ $params['cbs']['wc_gallery'] = __NAMESPACE__ . '\\_get_product_gallery_image_details';
73
+ }
74
+
75
+ if ( $is_product_category ) {
76
+ $params['cbs']['wc_thumbnail'] = __NAMESPACE__ . '\\_get_product_category_thumbnail_image_details';
77
+ }
78
+
79
+ return $params;
80
+ }
81
+
82
+ /**
83
+ * Generates image URLs and IDs from the WooCommerce product gallary entries.
84
+ *
85
+ * @since 4.0.0
86
+ * @access private
87
+ * @generator
88
+ *
89
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
90
+ * Leave null to autodetermine query.
91
+ * @param string $size The size of the image to get.
92
+ * @yield array : {
93
+ * string url: The image URL location,
94
+ * int id: The image ID,
95
+ * }
96
+ */
97
+ function _get_product_gallery_image_details( $args = null, $size = 'full' ) {
98
+
99
+ $post_id = isset( $args['id'] ) ? $args['id'] : \the_seo_framework()->get_the_real_ID();
100
+
101
+ $attachment_ids = [];
102
+
103
+ if ( $post_id && \metadata_exists( 'post', $post_id, '_product_image_gallery' ) ) {
104
+ $product_image_gallery = \get_post_meta( $post_id, '_product_image_gallery', true );
105
+
106
+ $attachment_ids = array_map( 'absint', array_filter( explode( ',', $product_image_gallery ) ) );
107
+ }
108
+
109
+ if ( $attachment_ids ) {
110
+ foreach ( $attachment_ids as $id ) {
111
+ yield [
112
+ 'url' => \wp_get_attachment_image_url( $id, $size ),
113
+ 'id' => $id,
114
+ ];
115
  }
116
+ } else {
117
+ yield [
118
+ 'url' => '',
119
+ 'id' => 0,
120
+ ];
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Generates image URL and ID from the WooCommerce product category thumbnail entries.
126
+ *
127
+ * @since 4.0.0
128
+ * @access private
129
+ * @generator
130
+ *
131
+ * @param array|null $args The query arguments. Accepts 'id' and 'taxonomy'.
132
+ * Leave null to autodetermine query.
133
+ * @param string $size The size of the image to get.
134
+ * @yield array : {
135
+ * string url: The image URL location,
136
+ * int id: The image ID,
137
+ * }
138
+ */
139
+ function _get_product_category_thumbnail_image_details( $args = null, $size = 'full' ) {
140
+
141
+ $term_id = isset( $args['id'] ) ? $args['id'] : \the_seo_framework()->get_the_real_ID();
142
+
143
+ $thumbnail_id = \get_term_meta( $term_id, 'thumbnail_id', true ) ?: 0;
144
+
145
+ if ( $thumbnail_id ) {
146
+ yield [
147
+ 'url' => \wp_get_attachment_image_url( $thumbnail_id, $size ) ?: '',
148
+ 'id' => $thumbnail_id,
149
+ ];
150
+ } else {
151
+ yield [
152
+ 'url' => '',
153
+ 'id' => 0,
154
+ ];
155
+ }
156
  }
inc/compat/plugin-wpforo.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\Plugin\wpForo
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
@@ -36,17 +38,24 @@ function _wpforo_fix_page() {
36
  \add_filter( 'the_seo_framework_title_from_generation', __NAMESPACE__ . '\\_wpforo_filter_pre_title', 10, 2 );
37
 
38
  if ( $override['meta'] ) {
39
- \add_filter( 'get_canonical_url', function( $canonical_url, $post ) {
40
- return function_exists( '\\wpforo_get_request_uri' ) ? \wpforo_get_request_uri() : $canonical_url;
41
- }, 10, 2 );
42
- \add_filter( 'the_seo_framework_description_args', __NAMESPACE__ . '\\_wpforo_filter_description_arguments', 10, 3 );
 
 
 
 
43
 
44
  //* Remove wpforo SEO meta output.
45
  \remove_action( 'wp_head', 'wpforo_add_meta_tags', 1 );
46
  } else {
47
- \add_action( 'the_seo_framework_after_init', function() {
48
- \remove_action( 'wp_head', [ \the_seo_Framework(), 'html_output' ], 1 );
49
- } );
 
 
 
50
  }
51
  }
52
  }
@@ -57,32 +66,20 @@ function _wpforo_fix_page() {
57
  * @since 2.9.2
58
  * @since 3.1.0 1. No longer emits an error when no wpForo title is presented.
59
  * 2. Updated to support new title generation.
 
60
  * @access private
61
  *
62
- * @param string $title The filter title.
63
- * @param array $args The title arguments.
 
64
  * @return string $title The wpForo title.
65
  */
66
- function _wpforo_filter_pre_title( $title, $args ) {
67
- $wpforo_title = \wpforo_meta_title( '' ); //= Either &$title or [ $title, ... ];
68
- return is_array( $wpforo_title ) && ! empty( $wpforo_title[0] ) ? $wpforo_title[0] : $title;
69
- }
70
-
71
- /**
72
- * Fixes wpForo page descriptions.
73
- *
74
- * @since 2.9.2
75
- * @access private
76
- *
77
- * @param array $defaults The default arguments.
78
- * @param array $args The method caller arguments.
79
- * @return array The description default arguments.
80
- */
81
- function _wpforo_filter_description_arguments( $defaults, $args ) {
82
 
83
- //* Disable internal requests only. Undocumentable, to be fixed later.
84
- if ( empty( $args['social'] ) && empty( $args['get_custom_field'] ) )
85
- $defaults['get_custom_field'] = false;
 
86
 
87
- return $defaults;
88
  }
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\Plugin\wpForo
4
+ * @subpackage The_SEO_Framework\Compatibility
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
38
  \add_filter( 'the_seo_framework_title_from_generation', __NAMESPACE__ . '\\_wpforo_filter_pre_title', 10, 2 );
39
 
40
  if ( $override['meta'] ) {
41
+ \add_filter(
42
+ 'get_canonical_url',
43
+ function( $canonical_url, $post ) {
44
+ return function_exists( '\\wpforo_get_request_uri' ) ? \wpforo_get_request_uri() : $canonical_url;
45
+ },
46
+ 10,
47
+ 2
48
+ );
49
 
50
  //* Remove wpforo SEO meta output.
51
  \remove_action( 'wp_head', 'wpforo_add_meta_tags', 1 );
52
  } else {
53
+ \add_action(
54
+ 'the_seo_framework_after_init',
55
+ function() {
56
+ \remove_action( 'wp_head', [ \the_seo_framework(), 'html_output' ], 1 );
57
+ }
58
+ );
59
  }
60
  }
61
  }
66
  * @since 2.9.2
67
  * @since 3.1.0 1. No longer emits an error when no wpForo title is presented.
68
  * 2. Updated to support new title generation.
69
+ * @since 4.0.0 No longer overrules external queries.
70
  * @access private
71
  *
72
+ * @param string $title The filter title.
73
+ * @param array|null $args The query arguments. Contains 'id' and 'taxonomy'.
74
+ * Is null when query is autodetermined.
75
  * @return string $title The wpForo title.
76
  */
77
+ function _wpforo_filter_pre_title( $title = '', $args = null ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
+ if ( null === $args ) {
80
+ $wpforo_title = \wpforo_meta_title( '' ); //= Either &$title or [ $title, ... ];
81
+ $title = is_array( $wpforo_title ) && ! empty( $wpforo_title[0] ) ? $wpforo_title[0] : $title;
82
+ }
83
 
84
+ return $title;
85
  }
inc/compat/plugin-wpml.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\Plugin\WPML
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
@@ -21,7 +23,7 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() a
21
  * @since 2.8.0
22
  * @access private
23
  *
24
- * @param \WP_Screen $current_screen
25
  */
26
  function _wpml_do_current_screen_action( $current_screen = '' ) {
27
 
@@ -36,7 +38,7 @@ function _wpml_do_current_screen_action( $current_screen = '' ) {
36
  * @since 2.8.0
37
  * @access private
38
  *
39
- * @param array $languages_links
40
  * @return array
41
  */
42
  function _wpml_remove_all_languages( $languages_links = [] ) {
@@ -55,8 +57,8 @@ function _wpml_remove_all_languages( $languages_links = [] ) {
55
  * @access private
56
  * @staticvar bool $cleared
57
  *
58
- * @param string $type The type. Comes in handy when you use a catch-all function.
59
- * @param int $id The post, page or TT ID. Defaults to $this->get_the_real_ID().
60
  * @param array $args Additional arguments. They can overwrite $type and $id.
61
  * @param bool $success Whether the action cleared.
62
  */
@@ -76,7 +78,7 @@ function _wpml_flush_sitemap( $type, $id, $args, $success ) {
76
  ); // No cache OK. DB call ok.
77
 
78
  //? We didn't use a wildcard after "_transient_" to reduce scans.
79
- // A second query is faster on saturated sites.
80
  $wpdb->query(
81
  $wpdb->prepare(
82
  "DELETE FROM $wpdb->options WHERE option_name LIKE %s",
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\Plugin\WPML
4
+ * @subpackage The_SEO_Framework\Compatibility
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
23
  * @since 2.8.0
24
  * @access private
25
  *
26
+ * @param \WP_Screen $current_screen The current screen object.
27
  */
28
  function _wpml_do_current_screen_action( $current_screen = '' ) {
29
 
38
  * @since 2.8.0
39
  * @access private
40
  *
41
+ * @param array $languages_links A list of selectable languages.
42
  * @return array
43
  */
44
  function _wpml_remove_all_languages( $languages_links = [] ) {
57
  * @access private
58
  * @staticvar bool $cleared
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
  */
78
  ); // No cache OK. DB call ok.
79
 
80
  //? We didn't use a wildcard after "_transient_" to reduce scans.
81
+ //? A second query is faster on saturated sites.
82
  $wpdb->query(
83
  $wpdb->prepare(
84
  "DELETE FROM $wpdb->options WHERE option_name LIKE %s",
inc/compat/theme-genesis.php CHANGED
@@ -1,7 +1,9 @@
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\Theme\Genesis
 
4
  */
 
5
  namespace The_SEO_Framework;
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
@@ -14,9 +16,9 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() a
14
  * @since 2.8.0
15
  * @access private
16
  *
17
- * @param array $plugins, overwritten as this filter will fire the
18
- * detection, regardless of other SEO plugins.
19
- * @return array Plugins to detect.
20
  */
21
  function _disable_genesis_seo( $plugins ) {
22
 
@@ -42,9 +44,9 @@ function _disable_genesis_seo( $plugins ) {
42
  * @since 2.8.0
43
  * @since 3.1.0 Now filters empty fields.
44
  *
45
- * @param array $data The current term meta.
46
- * @param int $term_id The current term ID.
47
- * @return array The Genesis Term meta.
48
  */
49
  function _genesis_get_term_meta( $data = [], $term_id = 0 ) {
50
 
1
  <?php
2
  /**
3
  * @package The_SEO_Framework\Compat\Theme\Genesis
4
+ * @subpackage The_SEO_Framework\Compatibility
5
  */
6
+
7
  namespace The_SEO_Framework;
8
 
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = \the_seo_framework_class() and $this instanceof $_this or die;
16
  * @since 2.8.0
17
  * @access private
18
  *
19
+ * @param array $plugins The plugins to detect. Overwritten as this filter will fire the
20
+ * detection, regardless of other SEO plugins.
21
+ * @return array
22
  */
23
  function _disable_genesis_seo( $plugins ) {
24
 
44
  * @since 2.8.0
45
  * @since 3.1.0 Now filters empty fields.
46
  *
47
+ * @param array $data The current term meta.
48
+ * @param int $term_id The current term ID.
49
+ * @return array The updated term meta.
50
  */
51
  function _genesis_get_term_meta( $data = [], $term_id = 0 ) {
52
 
inc/compat/wp-470.php DELETED
@@ -1,10 +0,0 @@
1
- <?php
2
- /**
3
- * Holds copies of WordPress 4.7 functions for forward compatibilty.
4
- *
5
- * @package The_SEO_Framework\Compat\WP\470
6
- * @subpackage WordPress
7
- * @license GPLv2+
8
- */
9
-
10
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
 
 
 
 
 
 
 
 
 
 
inc/functions/api.php CHANGED
@@ -1,7 +1,6 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework
4
- * @subpackage The_SEO_Framework\API
5
  */
6
 
7
  namespace {
@@ -45,7 +44,7 @@ namespace {
45
  * Returns the database version of TSF.
46
  *
47
  * @since 3.1.0
48
- * @since 3.1.2 Now forces a string.
49
  *
50
  * @return string The database version. '0' if version isn't found.
51
  */
@@ -110,7 +109,6 @@ namespace The_SEO_Framework {
110
  * @uses THE_SEO_FRAMEWORK_DIR_PATH_TRAIT
111
  * @access private
112
  * @staticvar array $loaded
113
- * @TODO use this.
114
  *
115
  * @param string $file Where the trait is for. Must be lowercase.
116
  * @return bool True if loaded, false otherwise.
@@ -143,4 +141,19 @@ namespace The_SEO_Framework {
143
 
144
  return isset( $cache[ $caller ] ) ?: ( ( $cache[ $caller ] = true ) && false );
145
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\API
 
4
  */
5
 
6
  namespace {
44
  * Returns the database version of TSF.
45
  *
46
  * @since 3.1.0
47
+ * @since 3.1.2 Now casts to string.
48
  *
49
  * @return string The database version. '0' if version isn't found.
50
  */
109
  * @uses THE_SEO_FRAMEWORK_DIR_PATH_TRAIT
110
  * @access private
111
  * @staticvar array $loaded
 
112
  *
113
  * @param string $file Where the trait is for. Must be lowercase.
114
  * @return bool True if loaded, false otherwise.
141
 
142
  return isset( $cache[ $caller ] ) ?: ( ( $cache[ $caller ] = true ) && false );
143
  }
144
+
145
+ /**
146
+ * Adds and returns-to the bootstrap timer.
147
+ *
148
+ * @since 4.0.0
149
+ * @access private
150
+ * @staticvar $time The estimated total time for bootstrapping.
151
+ *
152
+ * @param int $add The time to add.
153
+ * @return int The accumulated time, roughly.
154
+ */
155
+ function _bootstrap_timer( $add = 0 ) {
156
+ static $time = 0;
157
+ return $time += $add;
158
+ }
159
  }
inc/functions/deprecated.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework
4
- * @subpackage Deprecated
5
  */
6
 
7
  /**
@@ -25,6 +25,7 @@ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
25
 
26
  /**
27
  * This file contains most functions that have been deprecated.
 
28
  *
29
  * @ignore
30
  *
@@ -75,7 +76,7 @@ function the_seo_framework_version() {
75
  * @since 3.1.0 Deprecated
76
  * @deprecated
77
  *
78
- * @param string version The two dot version: x.v
79
  * @return bool False plugin inactive or version compare yields negative results.
80
  */
81
  function the_seo_framework_dot_version( $version = '2.4' ) {
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\API
4
+ * @subpackage The_SEO_Framework\Debug\Deprecated
5
  */
6
 
7
  /**
25
 
26
  /**
27
  * This file contains most functions that have been deprecated.
28
+ * We don't want to rush removing these, as missing functions cause fatal errors.
29
  *
30
  * @ignore
31
  *
76
  * @since 3.1.0 Deprecated
77
  * @deprecated
78
  *
79
+ * @param string $version The two dot version: x.v
80
  * @return bool False plugin inactive or version compare yields negative results.
81
  */
82
  function the_seo_framework_dot_version( $version = '2.4' ) {
inc/functions/index.php ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The highest proof of virtue is to possess boundless power without abusing it.
4
+ *
5
+ * - Thomas Babington Macaulay, 1st Baron Macaulay
6
+ */
inc/functions/upgrade-suggestion.php CHANGED
@@ -1,8 +1,9 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework
4
- * @subpackage The_SEO_Framework\Suggestion
5
  */
 
6
  namespace The_SEO_Framework\Suggestion;
7
 
8
  /**
@@ -66,20 +67,22 @@ function _prepare() {
66
  if ( ! empty( $plugin['the-seo-framework-extension-manager/the-seo-framework-extension-manager.php'] ) ) return;
67
 
68
  /** @source https://github.com/sybrew/The-SEO-Framework-Extension-Manager/blob/34674828a9e79bf72584e23aaa4a82ea1f154229/bootstrap/envtest.php#L51-L62 */
69
- $_req = [
 
70
  'php' => [
71
  '5.5' => 50521,
72
  '5.6' => 50605,
73
  ],
74
  'wp' => '37965',
75
  ];
76
- $envtest = false;
77
 
78
  //? PHP_VERSION_ID is definitely defined, but let's keep it homonymous with the envtest of TSFEM.
 
79
  ! defined( 'PHP_VERSION_ID' ) || PHP_VERSION_ID < $_req['php']['5.5'] and $envtest = 1
80
  or PHP_VERSION_ID >= 50600 && PHP_VERSION_ID < $_req['php']['5.6'] and $envtest = 2
81
  or $GLOBALS['wp_db_version'] < $_req['wp'] and $envtest = 3
82
  or $envtest = true;
 
83
 
84
  //? 5
85
  if ( true !== $envtest ) return;
@@ -107,12 +110,19 @@ function _suggest_extension_manager() {
107
 
108
  $tsf = \the_seo_framework();
109
 
110
- $tsf->do_dismissible_notice( $tsf->convert_markdown(
111
- sprintf(
112
- /* translators: %s = Extension URL markdown */
113
- \esc_html__( "Looking for more SEO functionality? Check out [The SEO Framework's extensions](%s).", 'autodescription' ),
114
- 'https://theseoframework.com/extensions/'
 
 
 
 
 
115
  ),
116
- [ 'a' ]
117
- ), 'updated', false, false );
 
 
118
  }
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Suggestion
4
+ * @subpackage The_SEO_Framework\Bootstrap\Install
5
  */
6
+
7
  namespace The_SEO_Framework\Suggestion;
8
 
9
  /**
67
  if ( ! empty( $plugin['the-seo-framework-extension-manager/the-seo-framework-extension-manager.php'] ) ) return;
68
 
69
  /** @source https://github.com/sybrew/The-SEO-Framework-Extension-Manager/blob/34674828a9e79bf72584e23aaa4a82ea1f154229/bootstrap/envtest.php#L51-L62 */
70
+ $envtest = false;
71
+ $_req = [
72
  'php' => [
73
  '5.5' => 50521,
74
  '5.6' => 50605,
75
  ],
76
  'wp' => '37965',
77
  ];
 
78
 
79
  //? PHP_VERSION_ID is definitely defined, but let's keep it homonymous with the envtest of TSFEM.
80
+ // phpcs:disable, Generic.Formatting.MultipleStatementAlignment, WordPress.WhiteSpace.PrecisionAlignment
81
  ! defined( 'PHP_VERSION_ID' ) || PHP_VERSION_ID < $_req['php']['5.5'] and $envtest = 1
82
  or PHP_VERSION_ID >= 50600 && PHP_VERSION_ID < $_req['php']['5.6'] and $envtest = 2
83
  or $GLOBALS['wp_db_version'] < $_req['wp'] and $envtest = 3
84
  or $envtest = true;
85
+ // phpcs:enable, Generic.Formatting.MultipleStatementAlignment, WordPress.WhiteSpace.PrecisionAlignment
86
 
87
  //? 5
88
  if ( true !== $envtest ) return;
110
 
111
  $tsf = \the_seo_framework();
112
 
113
+ $tsf->do_dismissible_notice(
114
+ $tsf->convert_markdown(
115
+ sprintf(
116
+ '**A word from Sybre, the developer of The SEO Framework:** We spent 3000 hours on version 4.0 of The SEO Framework, optimizing every aspect of the plugin, resulting in [1000 changes](%s), making this the highest performing SEO plugin! We humbly believe it shows we put you and your website first. There are still no ads or constant annoyances in your dashboard. As a result, many of our users don\'t know about [The SEO Framework &mdash; Extension Manager](%s). The extensions give extra SEO functionality, like **structured data for publishers**, **focus subject analysis**, and **spam protection**. All programmed with same meticulous standards. Many of the extensions are **free**, and the paid ones help to finance all our work. Consider [giving them a try](%s). Thank you.',
117
+ 'https://theseoframework.com/about/an-introduction-to-a-thousand-changes/',
118
+ 'https://theseoframework.com/extension-manager/',
119
+ 'https://theseoframework.com/extensions/'
120
+ ),
121
+ [ 'a', 'strong' ],
122
+ [ 'a_internal' => false ]
123
  ),
124
+ 'updated',
125
+ false,
126
+ false
127
+ );
128
  }
inc/interfaces/debug.interface.php DELETED
@@ -1,92 +0,0 @@
1
- <?php
2
- /**
3
- * @package The_SEO_Framework\Classes
4
- */
5
- namespace The_SEO_Framework;
6
-
7
- defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
8
-
9
- /**
10
- * The SEO Framework plugin
11
- * Copyright (C) 2015 - 2019 Sybre Waaijer, CyberWire (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
- /**
27
- * Interface The_SEO_Framework\Debug_Interface
28
- *
29
- * Sets public debug functions.
30
- *
31
- * @since 2.8.0
32
- */
33
- interface Debug_Interface {
34
-
35
- /**
36
- * Mark a filter as deprecated and inform when it has been used.
37
- *
38
- * @since 2.8.0
39
- * @access private
40
- * @see @this->_deprecated_function().
41
- *
42
- * @param string $filter The function that was called.
43
- * @param string $version The version of WordPress that deprecated the function.
44
- * @param string $replacement Optional. The function that should have been called. Default null.
45
- */
46
- public function _deprecated_filter( $filter, $version, $replacement = null ); // phpcs:ignore -- Internal function.
47
-
48
- /**
49
- * Mark a function as deprecated and inform when it has been used.
50
- *
51
- * Taken from WordPress core, but added extra parameters and linguistic alterations.
52
- *
53
- * The current behavior is to trigger a user error if WP_DEBUG is true.
54
- *
55
- * @since 2.6.0
56
- * @access private
57
- *
58
- * @param string $function The function that was called.
59
- * @param string $version The version of WordPress that deprecated the function.
60
- * @param string $replacement Optional. The function that should have been called. Default null.
61
- */
62
- public function _deprecated_function( $function, $version, $replacement = null ); // phpcs:ignore -- Internal function.
63
-
64
- /**
65
- * Mark a function as deprecated and inform when it has been used.
66
- *
67
- * Taken from WordPress core, but added extra parameters and linguistic alterations.
68
- *
69
- * The current behavior is to trigger a user error if WP_DEBUG is true.
70
- *
71
- * @since 2.6.0
72
- * @access private
73
- *
74
- * @param string $function The function that was called.
75
- * @param string $message A message explaining what has been done incorrectly.
76
- * @param string $version The version of WordPress where the message was added.
77
- */
78
- public function _doing_it_wrong( $function, $message, $version ); // phpcs:ignore -- Internal function.
79
-
80
- /**
81
- * Mark a property or method inaccessible when it has been used.
82
-
83
- * The current behavior is to trigger a user error if WP_DEBUG is true.
84
- *
85
- * @since 2.7.0
86
- * @access private
87
- *
88
- * @param string $p_or_m The Property or Method.
89
- * @param string $message A message explaining what has been done incorrectly.
90
- */
91
- public function _inaccessible_p_or_m( $p_or_m, $message = '' );
92
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/traits/core/index.php ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * As for me, I consider myself as a speck of the dust of the devotee's feet.
4
+ *
5
+ * - Sri Ramakrishna Paramahansa
6
+ */
inc/traits/core/overload.trait.php ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Traits\Overload
4
+ */
5
+
6
+ namespace The_SEO_Framework\Traits;
7
+
8
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) or die;
9
+
10
+ /**
11
+ * The SEO Framework plugin
12
+ * Copyright (C) 2019 Sybre Waaijer, CyberWire (https://cyberwire.nl/)
13
+ *
14
+ * This program is free software: you can redistribute it and/or modify
15
+ * it under the terms of the GNU General Public License version 3 as published
16
+ * by the Free Software Foundation.
17
+ *
18
+ * This program is distributed in the hope that it will be useful,
19
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
+ * GNU General Public License for more details.
22
+ *
23
+ * You should have received a copy of the GNU General Public License
24
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
25
+ */
26
+
27
+ /**
28
+ * Legend/Definitions:
29
+ *
30
+ * - Facade Legend/Definitions:
31
+ * Choose one per trait.
32
+ *
33
+ * - Core: Final instance.
34
+ * - No parents.
35
+ * - Maybe children.
36
+ * - All methods are protected.
37
+ *
38
+ * - Master: First instance of facade. Calls all the shots.
39
+ * - Expects parent class.
40
+ * - Has no child class.
41
+ * - All methods are public.
42
+ * - Except for subconstructor.
43
+ * - Expects class to be labelled "final".
44
+ *
45
+ * - Sub: Sub instance.
46
+ * - Expects child class.
47
+ * - Expects parent class.
48
+ *
49
+ * - Child: Child instance.
50
+ * - Synonymous to "static".
51
+ * - Expects parent class.
52
+ * - Could have child class.
53
+ * - Prevents object calling.
54
+ *
55
+ * - Stray: Expects nothing.
56
+ * - Maybe child.
57
+ * - Maybe parent.
58
+ *
59
+ * - Visibility Legend/Definitions:
60
+ * These can be combined.
61
+ *
62
+ * - Final: Final instance.
63
+ * - Expects children classes not to contain same methods.
64
+ * - All methods are labelled "final".
65
+ * - Expects class to be labelled "final".
66
+ *
67
+ * - Solo: Single object.
68
+ * - Expects no parents.
69
+ * - Expects no children.
70
+ * - All methods are labelled "final".
71
+ * - Expects class to be labelled "final".
72
+ * - Prevents facade pattern.
73
+ * - All methods could be public.
74
+ *
75
+ * - Static: Expects class not to be initiated.
76
+ * - Synonymous to "child".
77
+ * - Prevents object calling.
78
+ * - All public methods are static.
79
+ *
80
+ * - Once: Expects class to be called at most once.
81
+ * - Caches method calls.
82
+ * - Exits PHP on second call.
83
+ *
84
+ * - Interface: Contains abstract methods.
85
+ *
86
+ * - Private: All methods are private.
87
+ *
88
+ * - <No keyword>: Expects nothing.
89
+ * - All methods are "protected".
90
+ *
91
+ * - Public: All methods are public.
92
+ *
93
+ * - Type Legend/Definitions:
94
+ * Choose one per trait.
95
+ *
96
+ * - Enclose: Prevents common hacking methods through magic method nullification.
97
+ *
98
+ * - Construct: Holds constructor.
99
+ * - When interface: Holds subsconstructor.
100
+ * - Make sure the subconstructor is private. Otherwise late static binding will kick in.
101
+ *
102
+ * - Destruct: Holds destructor and keeps track of destruct calling.
103
+ *
104
+ * - Ignore_Properties_Core_Public_Final: Ignores invalid property calling. Prevents PHP warning messages.
105
+ *
106
+ * - <No keyword>: Should not exist.
107
+ */
108
+
109
+ // phpcs:disable, Squiz.Commenting.FunctionComment.Missing -- The trait doc explains it.
110
+ // phpcs:disable, Generic.Files.OneObjectStructurePerFile.MultipleFound -- This is a collective, preloaded file for all overloading.
111
+
112
+ /**
113
+ * Holds private overloading functions to prevent injection or abstraction.
114
+ *
115
+ * @since 4.0.0
116
+ * @access private
117
+ */
118
+ trait Enclose_Stray_Private {
119
+
120
+ private function __clone() { }
121
+
122
+ private function __wakeup() { }
123
+ }
124
+
125
+ /**
126
+ * Forces all classes and subclasses to prevent injection or abstraction.
127
+ *
128
+ * @since 4.0.0
129
+ * @access private
130
+ */
131
+ trait Enclose_Core_Final {
132
+
133
+ final protected function __clone() { }
134
+
135
+ final protected function __wakeup() { }
136
+ }
inc/traits/index.php ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * vayadhammā saṅkhārā appamādena sampādethāti.
4
+ * - Siddhārtha (aka Buddha)
5
+ *
6
+ * (Roughly translated by Sybre:)
7
+ * "Gather your [broken] pieces so you can make something [else] complete."
8
+ *
9
+ * In other words, when all seems lost, make the most of what you've got.
10
+ * Or, when your home falls apart, repurpose the rubble and make a new home.
11
+ *
12
+ * This, of course, relates to "traits" in PHP: They are pieces of something that you must complete.
13
+ * That's how I purpose all index.php quotes, if you haven't noticed yet.
14
+ */
inc/views/{metaboxes → admin/metaboxes}/description-metabox.php RENAMED
@@ -1,7 +1,7 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Views\Admin
4
- * @subpackage The_SEO_Framework\Views\Metaboxes
5
  */
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = the_seo_framework_class() and $this instanceof $_this or die;
@@ -14,7 +14,9 @@ switch ( $instance ) :
14
  ?>
15
  <h4><?php printf( esc_html__( 'Description Settings', 'autodescription' ) ); ?></h4>
16
  <?php
17
- $this->description( __( 'The meta description can be used to determine the text used under the title on search engine results pages.', 'autodescription' ) );
 
 
18
 
19
  ?>
20
  <hr>
@@ -28,11 +30,10 @@ switch ( $instance ) :
28
  __( 'Open Graph and Twitter Cards require descriptions. Therefore, it is best to leave this option enabled.', 'autodescription' )
29
  );
30
 
31
- //* Echo checkboxes.
32
  $this->wrap_fields(
33
  $this->make_checkbox(
34
  'auto_description',
35
- __( 'Automatically generate description?', 'autodescription' ),
36
  '',
37
  true
38
  ),
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Views\Admin\Metaboxes
4
+ * @subpackage The_SEO_Framework\Admin\Settings
5
  */
6
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = the_seo_framework_class() and $this instanceof $_this or die;
14
  ?>
15
  <h4><?php printf( esc_html__( 'Description Settings', 'autodescription' ) ); ?></h4>
16
  <?php
17
+ $this->description(
18
+ __( 'The meta description can be used to determine the text used under the title on search engine results pages.', 'autodescription' )
19
+ );
20
 
21
  ?>
22
  <hr>
30
  __( 'Open Graph and Twitter Cards require descriptions. Therefore, it is best to leave this option enabled.', 'autodescription' )
31
  );
32
 
 
33
  $this->wrap_fields(
34
  $this->make_checkbox(
35
  'auto_description',
36
+ __( 'Automatically generate descriptions?', 'autodescription' ),
37
  '',
38
  true
39
  ),
inc/views/admin/metaboxes/feed-metabox.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * @package The_SEO_Framework\Views\Admin\Metaboxes
4
+ * @subpackage The_SEO_Framework\Admin\Settings
5
+ */
6
+
7
+ defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = the_seo_framework_class() and $this instanceof $_this or die;
8
+
9
+ //* Fetch the required instance within this file.
10
+ $instance = $this->get_view_instance( 'the_seo_framework_feed_metabox', $instance );
11
+
12
+ switch ( $instance ) :
13
+ case 'the_seo_framework_feed_metabox_main':
14
+ ?>
15
+ <h4><?php esc_html_e( 'Content Feed Settings', 'autodescription' ); ?></h4>
16
+ <?php
17
+ $this->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' ) );
18
+ $this->description( __( 'Adding a backlink below the feed entries will also let the visitors know where the content came from.', 'autodescription' ) );
19
+
20
+ ?>
21
+ <hr>
22
+
23
+ <h4><?php esc_html_e( 'Change Feed Settings', 'autodescription' ); ?></h4>
24
+ <?php
25
+ $excerpt_the_feed_label = esc_html__( 'Convert feed entries into excerpts?', 'autodescription' );
26
+ $excerpt_the_feed_label .= ' ' . $this->make_info( __( 'By default the excerpt will be at most 400 characters long.', 'autodescription' ), '', false );
27
+
28
+ $source_the_feed_label = esc_html__( 'Add link to source below the feed entry content?', 'autodescription' );
29
+ $source_the_feed_label .= ' ' . $this->make_info( __( 'This link will not be followed by search engines.', 'autodescription' ), '', false );
30
+
31
+ $this->wrap_fields(
32
+ [
33
+ $this->make_checkbox( 'excerpt_the_feed', $excerpt_the_feed_label, '', false ),
34
+ $this->make_checkbox( 'source_the_feed', $source_the_feed_label, '', false ),
35
+ ],
36
+ true
37
+ );
38
+
39
+ if ( $this->rss_uses_excerpt() ) {
40
+ $this->description_noesc(
41
+ $this->convert_markdown(
42
+ sprintf(
43
+ /* translators: %s = Reading Settings URL. Links are in Markdown! */
44
+ esc_html__( 'Note: The feed is already converted into an excerpt through the [Reading Settings](%s).', 'autodescription' ),
45
+ esc_url( admin_url( 'options-reading.php' ) )
46
+ ),
47
+ [ 'a' ],
48
+ [ 'a_internal' => false ] // open in new window, although it's internal.
49
+ )
50
+ );
51
+ }
52
+
53
+ $this->description_noesc(
54
+ sprintf(
55
+ '<a href="%s" target=_blank rel=noopener>%s</a>',
56
+ esc_url( get_feed_link(), [ 'https', 'http' ] ),
57
+ esc_html__( 'View the main feed.', 'autodescription' )
58
+ )
59
+ );
60
+ break;
61
+
62
+ default:
63
+ break;
64
+ endswitch;
inc/views/{metaboxes → admin/metaboxes}/general-metabox.php RENAMED
@@ -1,45 +1,44 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Views\Admin
4
- * @subpackage The_SEO_Framework\Views\Metaboxes
5
  */
6
 
 
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = the_seo_framework_class() and $this instanceof $_this or die;
8
 
 
 
9
  //* Fetch the required instance within this file.
10
  $instance = $this->get_view_instance( 'the_seo_framework_general_metabox', $instance );
11
 
12
  switch ( $instance ) :
13
  case 'the_seo_framework_general_metabox_main':
14
  $default_tabs = [
15
- // 'general' => [
16
- // 'name' => __( 'General', 'autodescription' ),
17
- // 'callback' => [ $this, 'general_metabox_general_tab' ],
18
- // 'dashicon' => 'admin-generic',
19
- // ],
20
  'layout' => [
21
  'name' => __( 'Layout', 'autodescription' ),
22
- 'callback' => [ $this, 'general_metabox_layout_tab' ],
23
  'dashicon' => 'screenoptions',
24
  ],
25
  'performance' => [
26
  'name' => __( 'Performance', 'autodescription' ),
27
- 'callback' => [ $this, 'general_metabox_performance_tab' ],
28
  'dashicon' => 'performance',
29
  ],
30
  'canonical' => [
31
  'name' => __( 'Canonical', 'autodescription' ),
32
- 'callback' => [ $this, 'general_metabox_canonical_tab' ],
33
  'dashicon' => 'external',
34
  ],
35
  'timestamps' => [
36
  'name' => __( 'Timestamps', 'autodescription' ),
37
- 'callback' => [ $this, 'general_metabox_timestamps_tab' ],
38
  'dashicon' => 'clock',
39
  ],
40
- 'posttypes' => [
41
  'name' => __( 'Post Types', 'autodescription' ),
42
- 'callback' => [ $this, 'general_metabox_posttypes_tab' ],
43
  'dashicon' => 'index-card',
44
  ],
45
  ];
@@ -53,11 +52,7 @@ switch ( $instance ) :
53
 
54
  $tabs = wp_parse_args( $args, $defaults );
55
 
56
- $this->nav_tab_wrapper( 'general', $tabs, '2.8.0' );
57
- break;
58
-
59
- case 'the_seo_framework_general_metabox_general':
60
- echo 'Nothing to see here yet.';
61
  break;
62
 
63
  case 'the_seo_framework_general_metabox_layout':
@@ -71,20 +66,33 @@ switch ( $instance ) :
71
 
72
  <h4><?php esc_html_e( 'SEO Bar Settings', 'autodescription' ); ?></h4>
73
  <?php
74
- $this->wrap_fields( [
75
- $this->make_checkbox(
76
- 'display_seo_bar_tables',
77
- esc_html__( 'Display the SEO Bar in overview tables?', 'autodescription' ),
78
- '',
79
- false
80
- ),
81
- $this->make_checkbox(
82
- 'display_seo_bar_metabox',
83
- esc_html__( 'Display the SEO Bar in the SEO Settings metabox?', 'autodescription' ),
84
- '',
85
- false
86
- ),
87
- ], true );
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
  ?>
90
  <hr>
@@ -97,26 +105,30 @@ switch ( $instance ) :
97
  '',
98
  false
99
  );
 
100
  $character_info = $this->make_info(
101
  __( 'The character counter is based on guidelines.', 'autodescription' ),
102
  '',
103
  false
104
  );
105
 
106
- $this->wrap_fields( [
107
- $this->make_checkbox(
108
- 'display_pixel_counter',
109
- esc_html__( 'Display pixel counters?', 'autodescription' ) . ' ' . $pixel_info,
110
- '',
111
- false
112
- ),
113
- $this->make_checkbox(
114
- 'display_character_counter',
115
- esc_html__( 'Display character counters?', 'autodescription' ) . ' ' . $character_info,
116
- '',
117
- false
118
- ),
119
- ], true );
 
 
 
120
  break;
121
 
122
  case 'the_seo_framework_general_metabox_performance':
@@ -145,13 +157,13 @@ switch ( $instance ) :
145
  $query_types = (array) apply_filters(
146
  'the_seo_framework_query_alteration_types',
147
  [
148
- 'in_query' => _x( 'In the database', 'Perform query alteration...', 'autodescription' ),
149
- 'post_query' => _x( 'On the site', 'Perform query alteration...', 'autodescription' ),
150
  ]
151
  );
152
 
153
  $search_query_select_options = '';
154
- $_current = $this->get_option( 'alter_search_query_type' );
155
  foreach ( $query_types as $value => $name ) {
156
  $search_query_select_options .= vsprintf(
157
  '<option value="%s" %s>%s</option>',
@@ -162,8 +174,9 @@ switch ( $instance ) :
162
  ]
163
  );
164
  }
 
165
  $archive_query_select_options = '';
166
- $_current = $this->get_option( 'alter_archive_query_type' );
167
  foreach ( $query_types as $value => $name ) {
168
  $archive_query_select_options .= vsprintf(
169
  '<option value="%s" %s>%s</option>',
@@ -174,6 +187,7 @@ switch ( $instance ) :
174
  ]
175
  );
176
  }
 
177
  $perform_alteration_i18n = esc_html__( 'Perform alteration:', 'autodescription' );
178
 
179
  $search_query_select_field = vsprintf(
@@ -186,6 +200,7 @@ switch ( $instance ) :
186
  $search_query_select_options,
187
  ]
188
  );
 
189
  $archive_query_select_field = vsprintf(
190
  '<label for="%1$s">%2$s</label>
191
  <select name="%3$s" id="%1$s">%4$s</select>',
@@ -197,27 +212,33 @@ switch ( $instance ) :
197
  ]
198
  );
199
 
200
- $this->wrap_fields( [
201
- $this->make_checkbox(
202
- 'alter_search_query',
203
- esc_html__( 'Enable search query alteration?', 'autodescription' )
204
- . ' ' . $this->make_info( __( 'This allows you to exclude pages from on-site search results.', 'autodescription' ), '', false ),
205
- '',
206
- false
207
- ),
208
- $search_query_select_field,
209
- ], true );
210
-
211
- $this->wrap_fields( [
212
- $this->make_checkbox(
213
- 'alter_archive_query',
214
- esc_html__( 'Enable archive query alteration?', 'autodescription' )
215
- . ' ' . $this->make_info( __( 'This allows you to exclude pages from on-site archive listings.', 'autodescription' ), '', false ),
216
- '',
217
- false
218
- ),
219
- $archive_query_select_field,
220
- ], true );
 
 
 
 
 
 
221
  ?>
222
  <hr>
223
 
@@ -226,22 +247,25 @@ switch ( $instance ) :
226
  $this->description( __( 'To improve performance, generated SEO output can be stored in the database as transient cache.', 'autodescription' ) );
227
  $this->description( __( 'If your website has thousands of pages, or if other forms of caching are used, you might wish to adjust these options.', 'autodescription' ) );
228
 
229
- $this->wrap_fields( [
230
- $this->make_checkbox(
231
- 'cache_meta_schema',
232
- esc_html__( 'Enable automated Schema.org output cache?', 'autodescription' )
233
- . ' ' . $this->make_info( __( 'Schema.org output generally makes multiple calls to the database.', 'autodescription' ), '', false ),
234
- '',
235
- false
236
- ),
237
- $this->make_checkbox(
238
- 'cache_sitemap',
239
- esc_html__( 'Enable sitemap generation cache?', 'autodescription' )
240
- . ' ' . $this->make_info( __( 'Generating the sitemap can use a lot of server resources.', 'autodescription' ), '', false ),
241
- '',
242
- false
243
- ),
244
- ], true );
 
 
 
245
 
246
  if ( wp_using_ext_object_cache() ) :
247
  ?>
@@ -255,7 +279,7 @@ switch ( $instance ) :
255
  'cache_object',
256
  esc_html__( 'Enable object cache?', 'autodescription' )
257
  . ' ' . $this->make_info( __( 'Object cache generally works faster than transient cache.', 'autodescription' ), '', false ),
258
- esc_html__( 'An object cache handler has been detected. If you enable this option, you might wish to disable the Schema.org transient caching.', 'autodescription' ),
259
  false
260
  ),
261
  true
@@ -268,15 +292,13 @@ switch ( $instance ) :
268
  <h4><?php esc_html_e( 'Canonical URL Settings', 'autodescription' ); ?></h4>
269
  <?php
270
  $this->description( __( 'The canonical URL meta tag urges search engines to go to the outputted URL.', 'autodescription' ) );
271
- $this->description( __( 'If the canonical URL meta tag represents the visited page, then the search engine will crawl the visited page. Otherwise, the search engine might go to the outputted URL.', 'autodescription' ) );
272
- $this->description( __( 'Only adjust these options if you are aware of their SEO effects.', 'autodescription' ) );
273
  ?>
274
  <hr>
275
 
276
  <h4><?php esc_html_e( 'Scheme Settings', 'autodescription' ); ?></h4>
277
  <?php
278
- $this->description( __( 'If your website is accessible on both HTTP as HTTPS, set this to HTTPS in order to prevent duplicate content.', 'autodescription' ) );
279
- $this->description( __( 'Otherwise, automatic detection is recommended.', 'autodescription' ) );
280
  ?>
281
  <label for="<?php $this->field_id( 'canonical_scheme' ); ?>"><?php echo esc_html_x( 'Preferred canonical URL scheme:', '= Detect Automatically, HTTPS, HTTP', 'autodescription' ); ?></label>
282
  <select name="<?php $this->field_name( 'canonical_scheme' ); ?>" id="<?php $this->field_id( 'canonical_scheme' ); ?>">
@@ -284,7 +306,11 @@ switch ( $instance ) :
284
  $scheme_types = (array) apply_filters(
285
  'the_seo_framework_canonical_scheme_types',
286
  [
287
- 'automatic' => __( 'Detect automatically', 'autodescription' ),
 
 
 
 
288
  'http' => 'HTTP',
289
  'https' => 'HTTPS',
290
  ]
@@ -299,21 +325,41 @@ switch ( $instance ) :
299
  <h4><?php esc_html_e( 'Link Relationship Settings', 'autodescription' ); ?></h4>
300
  <?php
301
  $this->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' ) );
302
- $this->description( __( "It's recommended to turn these options on for better SEO consistency and to prevent duplicate content errors.", 'autodescription' ) );
303
-
304
- /* translators: %s = <code>rel</code> */
305
- $prev_next_posts_label = sprintf( esc_html__( 'Add %s link tags to posts and pages?', 'autodescription' ), $this->code_wrap( 'rel' ) );
306
- $prev_next_posts_checkbox = $this->make_checkbox( 'prev_next_posts', $prev_next_posts_label, '', false );
 
 
 
 
 
 
 
307
 
308
- /* translators: %s = <code>rel</code> */
309
- $prev_next_archives_label = sprintf( esc_html__( 'Add %s link tags to archives?', 'autodescription' ), $this->code_wrap( 'rel' ) );
310
- $prev_next_archives_checkbox = $this->make_checkbox( 'prev_next_archives', $prev_next_archives_label, '', false );
 
 
 
 
 
 
 
311
 
312
- /* translators: %s = <code>rel</code> */
313
- $prev_next_frontpage_label = sprintf( esc_html__( 'Add %s link tags to the homepage?', 'autodescription' ), $this->code_wrap( 'rel' ) );
314
- $prev_next_frontpage_checkbox = $this->make_checkbox( 'prev_next_frontpage', $prev_next_frontpage_label, '', false );
 
 
 
 
 
 
 
315
 
316
- //* Echo checkboxes.
317
  $this->wrap_fields( $prev_next_posts_checkbox . $prev_next_archives_checkbox . $prev_next_frontpage_checkbox, true );
318
  break;
319
 
@@ -336,7 +382,7 @@ switch ( $instance ) :
336
  ?>
337
  <h4><?php esc_html_e( 'Timestamp Settings', 'autodescription' ); ?></h4>
338
  <?php
339
- $this->description( __( 'Timestamps indicate when a page has been published and modified.', 'autodescription' ) );
340
  ?>
341
  <hr>
342
 
@@ -351,6 +397,7 @@ switch ( $instance ) :
351
  <input type="radio" name="<?php $this->field_name( 'timestamps_format' ); ?>" id="<?php $this->field_id( 'timestamps_format_0' ); ?>" value="0" <?php checked( $this->get_option( 'timestamps_format' ), '0' ); ?> />
352
  <label for="<?php $this->field_id( 'timestamps_format_0' ); ?>">
353
  <?php
 
354
  echo $this->code_wrap( $timestamp_0 );
355
  echo ' ';
356
  $this->make_info(
@@ -363,6 +410,7 @@ switch ( $instance ) :
363
  <input type="radio" name="<?php $this->field_name( 'timestamps_format' ); ?>" id="<?php $this->field_id( 'timestamps_format_1' ); ?>" value="1" <?php checked( $this->get_option( 'timestamps_format' ), '1' ); ?> />
364
  <label for="<?php $this->field_id( 'timestamps_format_1' ); ?>">
365
  <?php
 
366
  echo $this->code_wrap( $timestamp_1 );
367
  echo ' ';
368
  $this->make_info(
@@ -388,11 +436,11 @@ switch ( $instance ) :
388
  <h4><?php esc_html_e( 'Disable SEO', 'autodescription' ); ?></h4>
389
  <?php
390
  $this->description( __( 'Select post types which should not receive any SEO optimization whatsoever. This will remove meta optimizations, SEO suggestions, and sitemap inclusions for the selected post types.', 'autodescription' ) );
391
- $this->description( __( 'These settings are applied to the post type pages and their terms.', 'autodescription' ) );
392
  $this->description( __( 'Default post types can not be disabled.', 'autodescription' ) );
393
 
394
  $forced_pt = $this->get_forced_supported_post_types();
395
- $boxes = [];
396
 
397
  foreach ( $this->get_rewritable_post_types() as $post_type ) {
398
  $pto = get_post_type_object( $post_type );
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Views\Admin\Metaboxes
4
+ * @subpackage The_SEO_Framework\Admin\Settings
5
  */
6
 
7
+ use The_SEO_Framework\Bridges\SeoSettings;
8
+
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = the_seo_framework_class() and $this instanceof $_this or die;
10
 
11
+ // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
12
+
13
  //* Fetch the required instance within this file.
14
  $instance = $this->get_view_instance( 'the_seo_framework_general_metabox', $instance );
15
 
16
  switch ( $instance ) :
17
  case 'the_seo_framework_general_metabox_main':
18
  $default_tabs = [
 
 
 
 
 
19
  'layout' => [
20
  'name' => __( 'Layout', 'autodescription' ),
21
+ 'callback' => SeoSettings::class . '::_general_metabox_layout_tab',
22
  'dashicon' => 'screenoptions',
23
  ],
24
  'performance' => [
25
  'name' => __( 'Performance', 'autodescription' ),
26
+ 'callback' => SeoSettings::class . '::_general_metabox_performance_tab',
27
  'dashicon' => 'performance',
28
  ],
29
  'canonical' => [
30
  'name' => __( 'Canonical', 'autodescription' ),
31
+ 'callback' => SeoSettings::class . '::_general_metabox_canonical_tab',
32
  'dashicon' => 'external',
33
  ],
34
  'timestamps' => [
35
  'name' => __( 'Timestamps', 'autodescription' ),
36
+ 'callback' => SeoSettings::class . '::_general_metabox_timestamps_tab',
37
  'dashicon' => 'clock',
38
  ],
39
+ 'posttypes' => [
40
  'name' => __( 'Post Types', 'autodescription' ),
41
+ 'callback' => SeoSettings::class . '::_general_metabox_posttypes_tab',
42
  'dashicon' => 'index-card',
43
  ],
44
  ];
52
 
53
  $tabs = wp_parse_args( $args, $defaults );
54
 
55
+ SeoSettings::_nav_tab_wrapper( 'general', $tabs );
 
 
 
 
56
  break;
57
 
58
  case 'the_seo_framework_general_metabox_layout':
66
 
67
  <h4><?php esc_html_e( 'SEO Bar Settings', 'autodescription' ); ?></h4>
68
  <?php
69
+ $this->wrap_fields(
70
+ [
71
+ $this->make_checkbox(
72
+ 'display_seo_bar_tables',
73
+ esc_html__( 'Display the SEO Bar in overview tables?', 'autodescription' ),
74
+ '',
75
+ false
76
+ ),
77
+ $this->make_checkbox(
78
+ 'display_seo_bar_metabox',
79
+ esc_html__( 'Display the SEO Bar in the SEO Settings metabox?', 'autodescription' ),
80
+ '',
81
+ false
82
+ ),
83
+ $this->make_checkbox(
84
+ 'seo_bar_symbols',
85
+ esc_html__( 'Use symbols for warnings?', 'autodescription' ) . ' ' . $this->make_info(
86
+ __( 'If you have difficulty discerning colors, this may help you spot issues more easily.', 'autodescription' ),
87
+ '',
88
+ false
89
+ ),
90
+ '',
91
+ false
92
+ ),
93
+ ],
94
+ true
95
+ );
96
 
97
  ?>
98
  <hr>
105
  '',
106
  false
107
  );
108
+
109
  $character_info = $this->make_info(
110
  __( 'The character counter is based on guidelines.', 'autodescription' ),
111
  '',
112
  false
113
  );
114
 
115
+ $this->wrap_fields(
116
+ [
117
+ $this->make_checkbox(
118
+ 'display_pixel_counter',
119
+ esc_html__( 'Display pixel counters?', 'autodescription' ) . ' ' . $pixel_info,
120
+ '',
121
+ false
122
+ ),
123
+ $this->make_checkbox(
124
+ 'display_character_counter',
125
+ esc_html__( 'Display character counters?', 'autodescription' ) . ' ' . $character_info,
126
+ '',
127
+ false
128
+ ),
129
+ ],
130
+ true
131
+ );
132
  break;
133
 
134
  case 'the_seo_framework_general_metabox_performance':
157
  $query_types = (array) apply_filters(
158
  'the_seo_framework_query_alteration_types',
159
  [
160
+ 'in_query' => _x( 'In the database', 'Perform query alteration: In the database', 'autodescription' ),
161
+ 'post_query' => _x( 'On the site', 'Perform query alteration: On the site', 'autodescription' ),
162
  ]
163
  );
164
 
165
  $search_query_select_options = '';
166
+ $_current = $this->get_option( 'alter_search_query_type' );
167
  foreach ( $query_types as $value => $name ) {
168
  $search_query_select_options .= vsprintf(
169
  '<option value="%s" %s>%s</option>',
174
  ]
175
  );
176
  }
177
+
178
  $archive_query_select_options = '';
179
+ $_current = $this->get_option( 'alter_archive_query_type' );
180
  foreach ( $query_types as $value => $name ) {
181
  $archive_query_select_options .= vsprintf(
182
  '<option value="%s" %s>%s</option>',
187
  ]
188
  );
189
  }
190
+
191
  $perform_alteration_i18n = esc_html__( 'Perform alteration:', 'autodescription' );
192
 
193
  $search_query_select_field = vsprintf(
200
  $search_query_select_options,
201
  ]
202
  );
203
+
204
  $archive_query_select_field = vsprintf(
205
  '<label for="%1$s">%2$s</label>
206
  <select name="%3$s" id="%1$s">%4$s</select>',
212
  ]
213
  );
214
 
215
+ $this->wrap_fields(
216
+ [
217
+ $this->make_checkbox(
218
+ 'alter_search_query',
219
+ esc_html__( 'Enable search query alteration?', 'autodescription' )
220
+ . ' ' . $this->make_info( __( 'This allows you to exclude pages from on-site search results.', 'autodescription' ), '', false ),
221
+ '',
222
+ false
223
+ ),
224
+ $search_query_select_field,
225
+ ],
226
+ true
227
+ );
228
+
229
+ $this->wrap_fields(
230
+ [
231
+ $this->make_checkbox(
232
+ 'alter_archive_query',
233
+ esc_html__( 'Enable archive query alteration?', 'autodescription' )
234
+ . ' ' . $this->make_info( __( 'This allows you to exclude pages from on-site archive listings.', 'autodescription' ), '', false ),
235
+ '',
236
+ false
237
+ ),
238
+ $archive_query_select_field,
239
+ ],
240
+ true
241
+ );
242
  ?>
243
  <hr>
244
 
247
  $this->description( __( 'To improve performance, generated SEO output can be stored in the database as transient cache.', 'autodescription' ) );
248
  $this->description( __( 'If your website has thousands of pages, or if other forms of caching are used, you might wish to adjust these options.', 'autodescription' ) );
249
 
250
+ $this->wrap_fields(
251
+ [
252
+ $this->make_checkbox(
253
+ 'cache_meta_schema',
254
+ esc_html__( 'Enable automated Schema.org output cache?', 'autodescription' )
255
+ . ' ' . $this->make_info( __( 'Schema.org output generally makes multiple calls to the database.', 'autodescription' ), '', false ),
256
+ '',
257
+ false
258
+ ),
259
+ $this->make_checkbox(
260
+ 'cache_sitemap',
261
+ esc_html__( 'Enable sitemap generation cache?', 'autodescription' )
262
+ . ' ' . $this->make_info( __( 'Generating the sitemap can use a lot of server resources.', 'autodescription' ), '', false ),
263
+ '',
264
+ false
265
+ ),
266
+ ],
267
+ true
268
+ );
269
 
270
  if ( wp_using_ext_object_cache() ) :
271
  ?>
279
  'cache_object',
280
  esc_html__( 'Enable object cache?', 'autodescription' )
281
  . ' ' . $this->make_info( __( 'Object cache generally works faster than transient cache.', 'autodescription' ), '', false ),
282
+ esc_html__( 'An object cache handler has been detected. If you enable this option, you may wish to disable the Schema.org transient caching.', 'autodescription' ),
283
  false
284
  ),
285
  true
292
  <h4><?php esc_html_e( 'Canonical URL Settings', 'autodescription' ); ?></h4>
293
  <?php
294
  $this->description( __( 'The canonical URL meta tag urges search engines to go to the outputted URL.', 'autodescription' ) );
295
+ $this->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' ) );
 
296
  ?>
297
  <hr>
298
 
299
  <h4><?php esc_html_e( 'Scheme Settings', 'autodescription' ); ?></h4>
300
  <?php
301
+ $this->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' ) );
 
302
  ?>
303
  <label for="<?php $this->field_id( 'canonical_scheme' ); ?>"><?php echo esc_html_x( 'Preferred canonical URL scheme:', '= Detect Automatically, HTTPS, HTTP', 'autodescription' ); ?></label>
304
  <select name="<?php $this->field_name( 'canonical_scheme' ); ?>" id="<?php $this->field_id( 'canonical_scheme' ); ?>">
306
  $scheme_types = (array) apply_filters(
307
  'the_seo_framework_canonical_scheme_types',
308
  [
309
+ 'automatic' => sprintf(
310
+ /* translators: %s = HTTP or HTTPS */
311
+ __( 'Detect automatically (%s)', 'autodescription' ),
312
+ strtoupper( $this->detect_site_url_scheme() )
313
+ ),
314
  'http' => 'HTTP',
315
  'https' => 'HTTPS',
316
  ]
325
  <h4><?php esc_html_e( 'Link Relationship Settings', 'autodescription' ); ?></h4>
326
  <?php
327
  $this->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' ) );
328
+ $this->description( __( "It's recommended to turn these options on for better SEO consistency and to prevent duplicated content issues.", 'autodescription' ) );
329
+
330
+ $prev_next_posts_checkbox = $this->make_checkbox(
331
+ 'prev_next_posts',
332
+ $this->convert_markdown(
333
+ /* translators: the backticks are Markdown! Preserve them as-is! */
334
+ \esc_html__( 'Add `rel` link tags to posts and pages?', 'autodescription' ),
335
+ [ 'code' ]
336
+ ),
337
+ '',
338
+ false
339
+ );
340
 
341
+ $prev_next_archives_checkbox = $this->make_checkbox(
342
+ 'prev_next_archives',
343
+ $this->convert_markdown(
344
+ /* translators: the backticks are Markdown! Preserve them as-is! */
345
+ \esc_html__( 'Add `rel` link tags to archives?', 'autodescription' ),
346
+ [ 'code' ]
347
+ ),
348
+ '',
349
+ false
350
+ );
351
 
352
+ $prev_next_frontpage_checkbox = $this->make_checkbox(
353
+ 'prev_next_frontpage',
354
+ $this->convert_markdown(
355
+ /* translators: the backticks are Markdown! Preserve them as-is! */
356
+ \esc_html__( 'Add `rel` link tags to the homepage?', 'autodescription' ),
357
+ [ 'code' ]
358
+ ),
359
+ '',
360
+ false
361
+ );
362
 
 
363
  $this->wrap_fields( $prev_next_posts_checkbox . $prev_next_archives_checkbox . $prev_next_frontpage_checkbox, true );
364
  break;
365
 
382
  ?>
383
  <h4><?php esc_html_e( 'Timestamp Settings', 'autodescription' ); ?></h4>
384
  <?php
385
+ $this->description( __( 'Timestamps help indicate when a page has been published and modified.', 'autodescription' ) );
386
  ?>
387
  <hr>
388
 
397
  <input type="radio" name="<?php $this->field_name( 'timestamps_format' ); ?>" id="<?php $this->field_id( 'timestamps_format_0' ); ?>" value="0" <?php checked( $this->get_option( 'timestamps_format' ), '0' ); ?> />
398
  <label for="<?php $this->field_id( 'timestamps_format_0' ); ?>">
399
  <?php
400
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- code_wrap escapes.
401
  echo $this->code_wrap( $timestamp_0 );
402
  echo ' ';
403
  $this->make_info(
410
  <input type="radio" name="<?php $this->field_name( 'timestamps_format' ); ?>" id="<?php $this->field_id( 'timestamps_format_1' ); ?>" value="1" <?php checked( $this->get_option( 'timestamps_format' ), '1' ); ?> />
411
  <label for="<?php $this->field_id( 'timestamps_format_1' ); ?>">
412
  <?php
413
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- code_wrap escapes.
414
  echo $this->code_wrap( $timestamp_1 );
415
  echo ' ';
416
  $this->make_info(
436
  <h4><?php esc_html_e( 'Disable SEO', 'autodescription' ); ?></h4>
437
  <?php
438
  $this->description( __( 'Select post types which should not receive any SEO optimization whatsoever. This will remove meta optimizations, SEO suggestions, and sitemap inclusions for the selected post types.', 'autodescription' ) );
439
+ $this->description( __( 'These settings are applied 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' ) );
440
  $this->description( __( 'Default post types can not be disabled.', 'autodescription' ) );
441
 
442
  $forced_pt = $this->get_forced_supported_post_types();
443
+ $boxes = [];
444
 
445
  foreach ( $this->get_rewritable_post_types() as $post_type ) {
446
  $pto = get_post_type_object( $post_type );
inc/views/{metaboxes → admin/metaboxes}/homepage-metabox.php RENAMED
@@ -1,55 +1,53 @@
1
  <?php
2
  /**
3
- * @package The_SEO_Framework\Views\Admin
4
- * @subpackage The_SEO_Framework\Views\Metaboxes
5
  */
6
 
 
 
7
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = the_seo_framework_class() and $this instanceof $_this or die;
8
 
 
 
9
  //* Fetch the required instance within this file.
10
  $instance = $this->get_view_instance( 'the_seo_framework_homepage_metabox', $instance );
11
 
 
 
 
 
 
 
 
12
  switch ( $instance ) :
13
  case 'the_seo_framework_homepage_metabox_main':
14
- $this->description( __( 'These settings will take precedence over the settings set within the Home Page edit screen, if any.', 'autodescription' ) );
15
  ?>
16
  <hr>
17
  <?php
18
 
19
- /**
20
- * Parse tabs content.
21
- *
22
- * @since 2.6.0
23
- *
24
- * @param array $default_tabs { 'id' = The identifier =>
25
- * array(
26
- * 'name' => The name
27
- * 'callback' => The callback function, use array for method calling
28
- * 'dashicon' => Desired dashicon
29
- * )
30
- * }
31
- */
32
  $default_tabs = [
33
- 'general' => [
34
  'name' => __( 'General', 'autodescription' ),
35
- 'callback' => [ $this, 'homepage_metabox_general_tab' ],
36
  'dashicon' => 'admin-generic',
37
  ],
38
  'additions' => [
39
  'name' => __( 'Additions', 'autodescription' ),
40
- 'callback' => [ $this, 'homepage_metabox_additions_tab' ],
41
  'dashicon' => 'plus',
42
  ],
43
- 'robots' => [
44
- 'name' => __( 'Robots', 'autodescription' ),
45
- 'callback' => [ $this, 'homepage_metabox_robots_tab' ],
46
- 'dashicon' => 'visibility',
47
- ],
48
- 'social' => [
49
  'name' => __( 'Social', 'autodescription' ),
50
- 'callback' => [ $this, 'homepage_metabox_social_tab' ],
51
  'dashicon' => 'share',
52
  ],
 
 
 
 
 
53
  ];
54
 
55
  /**
@@ -61,98 +59,72 @@ switch ( $instance ) :
61
 
62
  $tabs = wp_parse_args( $args, $defaults );
63
 
64
- $this->nav_tab_wrapper( 'homepage', $tabs, '2.6.0' );
65
  break;
66
 
67
  case 'the_seo_framework_homepage_metabox_general':
68
- $language = $this->google_language();
69
- $home_id = $this->get_the_front_page_ID();
70
-
71
  $description_from_post_message = $title_from_post_message = '';
72
 
73
- $frompost_title = $this->has_page_on_front() ? $this->get_custom_field( '_genesis_title', $home_id ) : '';
74
  if ( $frompost_title ) {
75
  //! FIXME: Doesn't consider filters. Inject filter here, it's hackish...? Make a specific function, smelly...?
76
- if ( $this->use_title_branding( [ 'id' => $home_id ] ) ) {
77
- $this->merge_title_branding( $frompost_title, [ 'id' => $home_id ] );
78
  }
79
  $home_title_placeholder = $this->escape_title( $frompost_title );
80
  } else {
81
- $home_title_placeholder = $this->get_generated_title( [ 'id' => $home_id ] );
82
  }
83
 
84
- //* Fetch the description from the home page.
85
- $frompost_description = $this->has_page_on_front() ? $this->get_custom_field( '_genesis_description', $home_id ) : '';
86
 
87
- /**
88
- * Create a placeholder.
89
- * @since 2.3.4
90
- */
91
  if ( $frompost_description ) {
92
  $description_placeholder = $frompost_description;
93
  } else {
94
- $description_placeholder = $this->get_generated_description( [ 'id' => $home_id ] );
95
  }
96
-
97
- $tagline_placeholder = $this->s_title_raw( $this->get_blogdescription() );
98
-
99
  ?>
100
- <p>
101
- <label for="<?php $this->field_id( 'homepage_title_tagline' ); ?>" class="tsf-toblock">
102
- <strong><?php esc_html_e( 'Meta Title Additions', 'autodescription' ); ?></strong>
103
- </label>
104
- </p>
105
- <p>
106
- <input type="text" name="<?php $this->field_name( 'homepage_title_tagline' ); ?>" class="large-text" id="<?php $this->field_id( 'homepage_title_tagline' ); ?>" placeholder="<?php echo esc_attr( $tagline_placeholder ); ?>" value="<?php echo esc_attr( $this->get_option( 'homepage_title_tagline' ) ); ?>" autocomplete=off />
107
- </p>
108
 
109
- <hr>
110
-
111
- <div>
112
  <label for="<?php $this->field_id( 'homepage_title' ); ?>" class="tsf-toblock">
113
- <strong>
114
- <?php
115
- esc_html_e( 'Meta Title', 'autodescription' );
116
  echo ' ';
117
  $this->make_info(
118
  __( 'The meta title can be used to determine the title used on search engine result pages.', 'autodescription' ),
119
- 'https://support.google.com/webmasters/answer/35624?hl=' . $language . '#page-titles'
120
  );
121
- ?>
122
- </strong>
123
  </label>
124
  <?php
125
  //* Output these unconditionally, with inline CSS attached to allow reacting on settings.
126
  $this->output_character_counter_wrap( $this->get_field_id( 'homepage_title' ), '', (bool) $this->get_option( 'display_character_counter' ) );
127
  $this->output_pixel_counter_wrap( $this->get_field_id( 'homepage_title' ), 'title', (bool) $this->get_option( 'display_pixel_counter' ) );
128
  ?>
129
- </div>
130
  <p id="tsf-title-wrap">
131
- <input type="text" name="<?php $this->field_name( 'homepage_title' ); ?>" class="large-text" id="<?php $this->field_id( 'homepage_title' ); ?>" placeholder="<?php echo esc_attr( $home_title_placeholder ); ?>" value="<?php echo esc_attr( $this->get_option( 'homepage_title' ) ); ?>" autocomplete=off />
132
  <?php $this->output_js_title_elements(); ?>
133
  </p>
134
  <?php
135
- /**
136
- * If the home title is fetched from the post, notify about that instead.
137
- * @since 2.2.4
138
- *
139
- * Nesting often used translations
140
- */
141
- if ( $this->has_page_on_front() && $this->get_custom_field( '_genesis_title', $home_id ) ) {
142
- $this->description( __( 'Note: The title placeholder is fetched from the Page SEO Settings on the home page.', 'autodescription' ) );
143
  }
144
 
145
  /**
146
  * @since 2.8.0
147
- * @param bool $warn Whether to warn that there's a plugin active with multiple home pages.
148
  */
149
- if ( apply_filters( 'the_seo_framework_warn_homepage_global_title', false ) && $this->has_page_on_front() ) {
150
  $this->attention_noesc(
151
  //* Markdown escapes.
152
  $this->convert_markdown(
153
  sprintf(
154
- /* translators: %s = Home page URL markdown */
155
- esc_html__( 'A plugin has been detected that suggests to maintain this option on the [Home Page](%s).', 'autodescription' ),
156
  esc_url( admin_url( 'post.php?post=' . $home_id . '&action=edit#tsf-inpost-box' ) )
157
  ),
158
  [ 'a' ],
@@ -163,48 +135,46 @@ switch ( $instance ) :
163
  ?>
164
  <hr>
165
 
166
- <div>
167
  <label for="<?php $this->field_id( 'homepage_description' ); ?>" class="tsf-toblock">
168
- <strong>
169
- <?php
170
- esc_html_e( 'Meta Description', 'autodescription' );
171
  echo ' ';
172
  $this->make_info(
173
  __( 'The meta description can be used to determine the text used under the title on search engine results pages.', 'autodescription' ),
174
- 'https://support.google.com/webmasters/answer/35624?hl=' . $language . '#meta-descriptions'
175
  );
176
- ?>
177
- </strong>
178
  </label>
179
  <?php
180
  //* Output these unconditionally, with inline CSS attached to allow reacting on settings.
181
  $this->output_character_counter_wrap( $this->get_field_id( 'homepage_description' ), '', (bool) $this->get_option( 'display_character_counter' ) );
182
  $this->output_pixel_counter_wrap( $this->get_field_id( 'homepage_description' ), 'description', (bool) $this->get_option( 'display_pixel_counter' ) );
183
  ?>
184
- </div>
185
  <p>
186
  <textarea name="<?php $this->field_name( 'homepage_description' ); ?>" class="large-text" id="<?php $this->field_id( 'homepage_description' ); ?>" rows="3" cols="70" placeholder="<?php echo esc_attr( $description_placeholder ); ?>"><?php echo esc_attr( $this->get_option( 'homepage_description' ) ); ?></textarea>
187
- <?php echo $this->output_js_description_elements(); ?>
188
  </p>
189
  <?php
190
 
191
- if ( $this->has_page_on_front() && $this->get_custom_field( '_genesis_description', $home_id ) ) {
192
  $this->description(
193
- __( 'Note: The description placeholder is fetched from the Page SEO Settings on the home page.', 'autodescription' )
194
  );
195
  }
196
 
197
  /**
198
  * @since 2.8.0
199
- * @param bool $warn Whether to warn that there's a plugin active with multiple home pages.
200
  */
201
- if ( apply_filters( 'the_seo_framework_warn_homepage_global_description', false ) && $this->has_page_on_front() ) {
202
  $this->attention_noesc(
203
  //* Markdown escapes.
204
  $this->convert_markdown(
205
  sprintf(
206
- /* translators: %s = Home page URL markdown */
207
- esc_html__( 'A plugin has been detected that suggests to maintain this option on the [Home Page](%s).', 'autodescription' ),
208
  esc_url( admin_url( 'post.php?post=' . $home_id . '&action=edit#tsf-inpost-box' ) )
209
  ),
210
  [ 'a' ],
@@ -215,24 +185,50 @@ switch ( $instance ) :
215
  break;
216
 
217
  case 'the_seo_framework_homepage_metabox_additions':
 
 
218
  //* Fetches escaped title parts.
219
- $home_id = $this->get_the_front_page_ID();
220
  $_example_title = $this->escape_title(
221
- $this->get_raw_custom_field_title( [ 'id' => $home_id ] )
222
- ?: $this->get_raw_generated_title( [ 'id' => $home_id ] )
223
  );
224
  // FIXME? When no blog description or tagline is set... this will be empty and ugly on no-JS.
225
  $_example_blogname = $this->escape_title( $this->get_home_page_tagline() ?: $this->get_static_untitled_title() );
226
  $_example_separator = esc_html( $this->get_separator( 'title' ) );
227
 
228
- $example_left = '<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>';
229
- $example_right = '<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>';
 
230
 
231
  ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  <fieldset>
233
  <legend>
234
  <h4><?php esc_html_e( 'Meta Title Additions Location', 'autodescription' ); ?></h4>
235
- <?php $this->description( __( 'This setting determines which side the added title text will go on.', 'autodescription' ) ); ?>
236
  </legend>
237
 
238
  <p id="tsf-home-title-location" class="tsf-fields">
@@ -241,7 +237,7 @@ switch ( $instance ) :
241
  <label for="<?php $this->field_id( 'home_title_location_left' ); ?>">
242
  <span><?php esc_html_e( 'Left:', 'autodescription' ); ?></span>
243
  <?php
244
- //* Already escaped.
245
  echo $this->code_wrap_noesc( $example_left );
246
  ?>
247
  </label>
@@ -251,176 +247,27 @@ switch ( $instance ) :
251
  <label for="<?php $this->field_id( 'home_title_location_right' ); ?>">
252
  <span><?php esc_html_e( 'Right:', 'autodescription' ); ?></span>
253
  <?php
254
- //* Already escaped.
255
  echo $this->code_wrap_noesc( $example_right );
256
  ?>
257
  </label>
258
  </span>
259
  </p>
260
  </fieldset>
261
-
262
- <hr>
263
- <h4><?php esc_html_e( 'Title Additions', 'autodescription' ); ?></h4>
264
- <div id="tsf-title-tagline-toggle">
265
- <?php
266
- $this->wrap_fields(
267
- $this->make_checkbox(
268
- 'homepage_tagline',
269
- esc_html__( 'Add Meta Title Additions to the home page title?', 'autodescription' ),
270
- '',
271
- false
272
- ),
273
- true
274
- );
275
- ?>
276
- </div>
277
- <?php
278
- break;
279
-
280
- case 'the_seo_framework_homepage_metabox_robots':
281
- $language = $this->google_language();
282
-
283
- //* Get home page ID. If blog on front, it's 0.
284
- $home_id = $this->get_the_front_page_ID();
285
-
286
- $noindex_post = $home_id ? $this->get_custom_field( '_genesis_noindex', $home_id ) : '';
287
- $nofollow_post = $home_id ? $this->get_custom_field( '_genesis_nofollow', $home_id ) : '';
288
- $noarchive_post = $home_id ? $this->get_custom_field( '_genesis_noarchive', $home_id ) : '';
289
-
290
- $checked_home = '';
291
- /**
292
- * Shows user that the setting is checked on the home page.
293
- * Adds starting - with space to maintain readability.
294
- *
295
- * @since 2.2.4
296
- */
297
- if ( $noindex_post || $nofollow_post || $noarchive_post ) {
298
- $checked_home = ' - <a href="' . esc_url( admin_url( 'post.php?post=' . $home_id . '&action=edit#tsf-inpost-box' ) ) . '" target="_blank" class="attention" title="' . esc_attr__( 'View Home Page Settings', 'autodescription' ) . '" >' . esc_html__( 'Checked in Page', 'autodescription' ) . '</a>';
299
- }
300
-
301
- ?>
302
- <h4><?php esc_html_e( 'Robots Meta Settings', 'autodescription' ); ?></h4>
303
- <?php
304
-
305
- $noindex_note = $noindex_post ? $checked_home : '';
306
- $nofollow_note = $nofollow_post ? $checked_home : '';
307
- $noarchive_note = $noarchive_post ? $checked_home : '';
308
-
309
- //* Index label.
310
- /* translators: %s = noindex/nofollow/noarchive */
311
- $i_label = sprintf( esc_html__( 'Apply %s to the home page?', 'autodescription' ), $this->code_wrap( 'noindex' ) );
312
- $i_label .= ' ';
313
- $i_label .= $this->make_info(
314
- __( 'This tells search engines not to show this page in their search results.', 'autodescription' ),
315
- 'https://support.google.com/webmasters/answer/93710?hl=' . $language,
316
- false
317
- ) . $noindex_note;
318
-
319
- //* Follow label.
320
- /* translators: %s = noindex/nofollow/noarchive */
321
- $f_label = sprintf( esc_html__( 'Apply %s to the home page?', 'autodescription' ), $this->code_wrap( 'nofollow' ) );
322
- $f_label .= ' ';
323
- $f_label .= $this->make_info(
324
- __( 'This tells search engines not to follow links on this page.', 'autodescription' ),
325
- 'https://support.google.com/webmasters/answer/96569?hl=' . $language,
326
- false
327
- ) . $nofollow_note;
328
-
329
- //* Archive label.
330
- /* translators: %s = noindex/nofollow/noarchive */
331
- $a_label = sprintf( esc_html__( 'Apply %s to the home page?', 'autodescription' ), $this->code_wrap( 'noarchive' ) );
332
- $a_label .= ' ';
333
- $a_label .= $this->make_info(
334
- __( 'This tells search engines not to save a cached copy of this page.', 'autodescription' ),
335
- 'https://support.google.com/webmasters/answer/79812?hl=' . $language,
336
- false
337
- ) . $noarchive_note;
338
-
339
- $this->attention_description( __( 'Warning: No public site should ever disable indexing or following for the homepage.', 'autodescription' ) );
340
-
341
- //* Echo checkboxes.
342
- $this->wrap_fields( [
343
- $this->make_checkbox(
344
- 'homepage_noindex',
345
- $i_label,
346
- '',
347
- false
348
- ),
349
- $this->make_checkbox(
350
- 'homepage_nofollow',
351
- $f_label,
352
- '',
353
- false
354
- ),
355
- $this->make_checkbox(
356
- 'homepage_noarchive',
357
- $a_label,
358
- '',
359
- false
360
- ),
361
- ], true );
362
-
363
- // Add notice if any options are checked on the post.
364
- if ( $noindex_post || $nofollow_post || $noarchive_post ) {
365
- $this->attention_description( __( 'Note: If any of these options are unchecked, but are checked on the home page, they will be outputted regardless.', 'autodescription' ) );
366
- }
367
- ?>
368
-
369
- <hr>
370
-
371
- <h4><?php esc_html_e( 'Home Page Pagination Robots Settings', 'autodescription' ); ?></h4>
372
  <?php
373
- $this->description( __( "If your home page is paginated and outputs content that's also found elsewhere on the website, enabling this option might prevent duplicate content.", 'autodescription' ) );
374
-
375
- //* Echo checkbox.
376
- $this->wrap_fields(
377
- $this->make_checkbox(
378
- 'home_paged_noindex',
379
- /* translators: %s = noindex/nofollow/noarchive */
380
- sprintf( esc_html__( 'Apply %s to every second or later page on the home page?', 'autodescription' ), $this->code_wrap( 'noindex' ) ),
381
- '',
382
- false
383
- ),
384
- true
385
- );
386
  break;
387
 
388
  case 'the_seo_framework_homepage_metabox_social':
389
- $language = $this->google_language();
390
-
391
- //* Get home page ID. If blog on front, it's 0.
392
- $home_id = $this->get_the_front_page_ID();
393
-
394
  // Gets custom fields from page.
395
- $custom_og_title = $this->get_custom_field( '_open_graph_title', $home_id );
396
- $custom_og_desc = $this->get_custom_field( '_open_graph_description', $home_id );
397
- $custom_tw_title = $this->get_custom_field( '_twitter_title', $home_id );
398
- $custom_tw_desc = $this->get_custom_field( '_twitter_description', $home_id );
399
-
400
- // Gets custom fields from SEO settings.
401
- $home_og_title = $this->get_option( 'homepage_og_title' );
402
- $home_og_desc = $this->get_option( 'homepage_og_description' );
403
- // $home_tw_title = $this->get_option( 'homepage_twitter_title' );
404
- // $home_tw_desc = $this->get_option( 'homepage_twitter_description' );
405
-
406
- //! OG input falls back to default input.
407
- $og_tit_placeholder = $custom_og_title ?: $this->get_generated_open_graph_title( [ 'id' => $home_id ] );
408
- $og_desc_placeholder = $custom_og_desc
409
- ?: $this->get_description_from_custom_field( [ 'id' => $home_id ] )
410
- ?: $this->get_generated_open_graph_description( [ 'id' => $home_id ] );
411
-
412
- //! Twitter input falls back to OG input.
413
- $tw_tit_placeholder = $custom_tw_title ?: $home_og_title ?: $og_tit_placeholder;
414
- $tw_desc_placeholder = $custom_tw_desc
415
- ?: $home_og_desc
416
- ?: $custom_og_desc
417
- ?: $this->get_description_from_custom_field( [ 'id' => $home_id ] )
418
- ?: $this->get_generated_twitter_description( [ 'id' => $home_id ] );
419
 
420
- ?>
421
- <h4><?php esc_html_e( 'Open Graph Settings', 'autodescription' ); ?></h4>
422
 
423
- <div>
 
424
  <label for="<?php $this->field_id( 'homepage_og_title' ); ?>" class="tsf-toblock">
425
  <strong>
426
  <?php
@@ -432,19 +279,19 @@ switch ( $instance ) :
432
  //* Output this unconditionally, with inline CSS attached to allow reacting on settings.
433
  $this->output_character_counter_wrap( $this->get_field_id( 'homepage_og_title' ), '', (bool) $this->get_option( 'display_character_counter' ) );
434
  ?>
435
- </div>
436
  <p>
437
- <input type="text" name="<?php $this->field_name( 'homepage_og_title' ); ?>" class="large-text" id="<?php $this->field_id( 'homepage_og_title' ); ?>" placeholder="<?php echo esc_attr( $og_tit_placeholder ); ?>" value="<?php echo esc_attr( $this->get_option( 'homepage_og_title' ) ); ?>" autocomplete=off />
438
  </p>
439
  <?php
440
  if ( $this->has_page_on_front() && $custom_og_title ) {
441
  $this->description(
442
- __( 'Note: The title placeholder is fetched from the Page SEO Settings on the home page.', 'autodescription' )
443
  );
444
  }
445
  ?>
446
 
447
- <div>
448
  <label for="<?php $this->field_id( 'homepage_og_description' ); ?>" class="tsf-toblock">
449
  <strong>
450
  <?php
@@ -456,23 +303,21 @@ switch ( $instance ) :
456
  //* Output this unconditionally, with inline CSS attached to allow reacting on settings.
457
  $this->output_character_counter_wrap( $this->get_field_id( 'homepage_og_description' ), '', (bool) $this->get_option( 'display_character_counter' ) );
458
  ?>
459
- </div>
460
  <p>
461
- <textarea name="<?php $this->field_name( 'homepage_og_description' ); ?>" class="large-text" id="<?php $this->field_id( 'homepage_og_description' ); ?>" rows="3" cols="70" placeholder="<?php echo esc_attr( $og_desc_placeholder ); ?>"><?php echo esc_attr( $this->get_option( 'homepage_og_description' ) ); ?></textarea>
462
- <?php echo $this->output_js_description_elements(); ?>
463
  </p>
464
  <?php
465
  if ( $this->has_page_on_front() && $custom_og_desc ) {
466
  $this->description(
467
- __( 'Note: The description placeholder is fetched from the Page SEO Settings on the home page.', 'autodescription' )
468
  );
469
  }
470
  ?>
471
  <hr>
472
 
473
- <h4><?php esc_html_e( 'Twitter Settings', 'autodescription' ); ?></h4>
474
-
475
- <div>
476
  <label for="<?php $this->field_id( 'homepage_twitter_title' ); ?>" class="tsf-toblock">
477
  <strong>
478
  <?php
@@ -484,19 +329,19 @@ switch ( $instance ) :
484
  //* Output this unconditionally, with inline CSS attached to allow reacting on settings.
485
  $this->output_character_counter_wrap( $this->get_field_id( 'homepage_twitter_title' ), '', (bool) $this->get_option( 'display_character_counter' ) );
486
  ?>
487
- </div>
488
  <p>
489
- <input type="text" name="<?php $this->field_name( 'homepage_twitter_title' ); ?>" class="large-text" id="<?php $this->field_id( 'homepage_twitter_title' ); ?>" placeholder="<?php echo esc_attr( $tw_tit_placeholder ); ?>" value="<?php echo esc_attr( $this->get_option( 'homepage_twitter_title' ) ); ?>" autocomplete=off />
490
  </p>
491
  <?php
492
  if ( $this->has_page_on_front() && ( $custom_og_title || $custom_tw_title ) ) {
493
  $this->description(
494
- __( 'Note: The title placeholder is fetched from the Page SEO Settings on the home page.', 'autodescription' )
495
  );
496
  }
497
  ?>
498
 
499
- <div>
500
  <label for="<?php $this->field_id( 'homepage_twitter_description' ); ?>" class="tsf-toblock">
501
  <strong>
502
  <?php
@@ -508,15 +353,15 @@ switch ( $instance ) :
508
  //* Output this unconditionally, with inline CSS attached to allow reacting on settings.
509
  $this->output_character_counter_wrap( $this->get_field_id( 'homepage_twitter_description' ), '', (bool) $this->get_option( 'display_character_counter' ) );
510
  ?>
511
- </div>
512
  <p>
513
- <textarea name="<?php $this->field_name( 'homepage_twitter_description' ); ?>" class="large-text" id="<?php $this->field_id( 'homepage_twitter_description' ); ?>" rows="3" cols="70" placeholder="<?php echo esc_attr( $tw_desc_placeholder ); ?>"><?php echo esc_attr( $this->get_option( 'homepage_twitter_description' ) ); ?></textarea>
514
- <?php echo $this->output_js_description_elements(); ?>
515
  </p>
516
  <?php
517
  if ( $this->has_page_on_front() && ( $custom_og_desc || $custom_tw_desc ) ) {
518
  $this->description(
519
- __( 'Note: The description placeholder is fetched from the Page SEO Settings on the home page.', 'autodescription' )
520
  );
521
  }
522
  ?>
@@ -526,29 +371,9 @@ switch ( $instance ) :
526
  <?php
527
  $this->description( __( 'A social image can be displayed when your homepage is shared. It is a great way to grab attention.', 'autodescription' ) );
528
 
529
- //* Get the front-page ID. It's 0 if front page is blog.
530
- $page_id = $this->get_the_front_page_ID();
531
-
532
- if ( $this->has_page_on_front() ) {
533
- $image_args = [
534
- 'post_id' => $page_id,
535
- 'disallowed' => [
536
- 'homemeta',
537
- ],
538
- 'escape' => false,
539
- ];
540
- } else {
541
- $image_args = [
542
- 'post_id' => $page_id,
543
- 'disallowed' => [
544
- 'homemeta',
545
- 'postmeta',
546
- 'featured',
547
- ],
548
- 'escape' => false,
549
- ];
550
- }
551
- $image_placeholder = $this->get_social_image( $image_args );
552
 
553
  ?>
554
  <p>
@@ -556,7 +381,8 @@ switch ( $instance ) :
556
  <strong><?php esc_html_e( 'Social Image URL', 'autodescription' ); ?></strong>
557
  <?php
558
  $this->make_info(
559
- __( 'Set preferred homepage Social Image URL location.', 'autodescription' ), 'https://developers.facebook.com/docs/sharing/best-practices#images'
 
560
  );
561
  ?>
562
  </label>
@@ -565,15 +391,155 @@ switch ( $instance ) :
565
  <input class="large-text" type="url" name="<?php $this->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' ) ); ?>" />
566
  <input type="hidden" name="<?php $this->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" />
567
  </p>
568
- <p class="hide-if-no-js">
569
  <?php
570
- //* Already escaped.
571
  echo $this->get_social_image_uploader_form( 'tsf_homepage_socialimage' );
572
  ?>
573
  </p>
574
  <?php
575
  break;
576
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <?php
2
  /**
3
+ * @package The_SEO_Framework\Views\Admin\Metaboxes
4
+ * @subpackage The_SEO_Framework\Admin\Settings
5
  */
6
 
7
+ use The_SEO_Framework\Bridges\SeoSettings;
8
+
9
  defined( 'THE_SEO_FRAMEWORK_PRESENT' ) and $_this = the_seo_framework_class() and $this instanceof $_this or die;
10
 
11
+ // phpcs:disable, WordPress.WP.GlobalVariablesOverride -- This isn't the global scope.
12
+
13
  //* Fetch the required instance within this file.
14
  $instance = $this->get_view_instance( 'the_seo_framework_homepage_metabox', $instance );
15
 
16
+ $home_id = $this->get_the_front_page_ID();
17
+
18
+ $_generator_args = [
19
+ 'id' => $home_id,
20
+ 'taxonomy' => '',
21
+ ];
22
+
23
  switch ( $instance ) :
24
  case 'the_seo_framework_homepage_metabox_main':
25
+ $this->description( __( 'These settings will take precedence over the settings set within the homepage edit screen, if any.', 'autodescription' ) );
26
  ?>
27
  <hr>
28
  <?php
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  $default_tabs = [
31
+ 'general' => [
32
  'name' => __( 'General', 'autodescription' ),
33
+ 'callback' => SeoSettings::class . '::_homepage_metabox_general_tab',
34
  'dashicon' => 'admin-generic',
35
  ],
36
  'additions' => [
37
  'name' => __( 'Additions', 'autodescription' ),
38
+ 'callback' => SeoSettings::class . '::_homepage_metabox_additions_tab',
39
  'dashicon' => 'plus',
40
  ],
41
+ 'social' => [
 
 
 
 
 
42
  'name' => __( 'Social', 'autodescription' ),
43
+ 'callback' => SeoSettings::class . '::_homepage_metabox_social_tab',
44
  'dashicon' => 'share',
45
  ],
46
+ 'robots' => [
47
+ 'name' => __( 'Robots', 'autodescription' ),
48
+ 'callback' => SeoSettings::class . '::_homepage_metabox_robots_tab',
49
+ 'dashicon' => 'visibility',
50
+ ],
51
  ];
52
 
53
  /**
59
 
60
  $tabs = wp_parse_args( $args, $defaults );
61
 
62
+ SeoSettings::_nav_tab_wrapper( 'homepage', $tabs );
63
  break;
64
 
65
  case 'the_seo_framework_homepage_metabox_general':
 
 
 
66
  $description_from_post_message = $title_from_post_message = '';
67
 
68
+ $frompost_title = $home_id ? $this->get_post_meta_item( '_genesis_title', $home_id ) : '';
69
  if ( $frompost_title ) {
70
  //! FIXME: Doesn't consider filters. Inject filter here, it's hackish...? Make a specific function, smelly...?
71
+ if ( $this->use_title_branding( $_generator_args ) ) {
72
+ $this->merge_title_branding( $frompost_title, $_generator_args );
73
  }
74
  $home_title_placeholder = $this->escape_title( $frompost_title );
75
  } else {
76
+ $home_title_placeholder = $this->get_generated_title( $_generator_args );
77
  }
78
 
79
+ //* Fetch the description from the homepage.
80
+ $frompost_description = $home_id ? $this->get_post_meta_item( '_genesis_description', $home_id ) : '';
81
 
 
 
 
 
82
  if ( $frompost_description ) {
83
  $description_placeholder = $frompost_description;
84
  } else {
85
+ $description_placeholder = $this->get_generated_description( $_generator_args );
86
  }
 
 
 
87
  ?>
 
 
 
 
 
 
 
 
88
 
89
+ <p>
 
 
90
  <label for="<?php $this->field_id( 'homepage_title' ); ?>" class="tsf-toblock">
91
+ <strong><?php esc_html_e( 'Meta Title', 'autodescription' ); ?></strong>
92
+ <?php
 
93
  echo ' ';
94
  $this->make_info(
95
  __( 'The meta title can be used to determine the title used on search engine result pages.', 'autodescription' ),
96
+ 'https://support.google.com/webmasters/answer/35624#page-titles'
97
  );
98
+ ?>
 
99
  </label>
100
  <?php
101
  //* Output these unconditionally, with inline CSS attached to allow reacting on settings.
102
  $this->output_character_counter_wrap( $this->get_field_id( 'homepage_title' ), '', (bool) $this->get_option( 'display_character_counter' ) );
103
  $this->output_pixel_counter_wrap( $this->get_field_id( 'homepage_title' ), 'title', (bool) $this->get_option( 'display_pixel_counter' ) );
104
  ?>
105
+ </p>
106
  <p id="tsf-title-wrap">
107
+ <input type="text" name="<?php $this->field_name( 'homepage_title' ); ?>" class="large-text" id="<?php $this->field_id( 'homepage_title' ); ?>" placeholder="<?php echo esc_attr( $home_title_placeholder ); ?>" value="<?php echo $this->esc_attr_preserve_amp( $this->get_option( 'homepage_title' ) ); ?>" autocomplete=off />
108
  <?php $this->output_js_title_elements(); ?>
109
  </p>
110
  <?php
111
+ $this->description( __( 'Note: The input value of this field may be used to describe the name of the site elsewhere.', 'autodescription' ) );
112
+
113
+ if ( $home_id && $this->get_post_meta_item( '_genesis_title', $home_id ) ) {
114
+ $this->description( __( 'Note: The title placeholder is fetched from the Page SEO Settings on the homepage.', 'autodescription' ) );
 
 
 
 
115
  }
116
 
117
  /**
118
  * @since 2.8.0
119
+ * @param bool $warn Whether to warn that there's a plugin active with multiple homepages.
120
  */
121
+ if ( $home_id && apply_filters( 'the_seo_framework_warn_homepage_global_title', false ) ) {
122
  $this->attention_noesc(
123
  //* Markdown escapes.
124
  $this->convert_markdown(
125
  sprintf(
126
+ /* translators: %s = Homepage URL markdown */
127
+ esc_html__( 'A plugin has been detected that suggests to maintain this option on the [homepage](%s).', 'autodescription' ),
128
  esc_url( admin_url( 'post.php?post=' . $home_id . '&action=edit#tsf-inpost-box' ) )
129
  ),
130
  [ 'a' ],
135
  ?>
136
  <hr>
137
 
138
+ <p>
139
  <label for="<?php $this->field_id( 'homepage_description' ); ?>" class="tsf-toblock">
140
+ <strong><?php esc_html_e( 'Meta Description', 'autodescription' ); ?></strong>
141
+ <?php
 
142
  echo ' ';
143
  $this->make_info(
144
  __( 'The meta description can be used to determine the text used under the title on search engine results pages.', 'autodescription' ),
145
+ 'https://support.google.com/webmasters/answer/35624#meta-descriptions'
146
  );
147
+ ?>
 
148
  </label>
149
  <?php
150
  //* Output these unconditionally, with inline CSS attached to allow reacting on settings.
151
  $this->output_character_counter_wrap( $this->get_field_id( 'homepage_description' ), '', (bool) $this->get_option( 'display_character_counter' ) );
152
  $this->output_pixel_counter_wrap( $this->get_field_id( 'homepage_description' ), 'description', (bool) $this->get_option( 'display_pixel_counter' ) );
153
  ?>
154
+ </p>
155
  <p>
156
  <textarea name="<?php $this->field_name( 'homepage_description' ); ?>" class="large-text" id="<?php $this->field_id( 'homepage_description' ); ?>" rows="3" cols="70" placeholder="<?php echo esc_attr( $description_placeholder ); ?>"><?php echo esc_attr( $this->get_option( 'homepage_description' ) ); ?></textarea>
157
+ <?php $this->output_js_description_elements(); ?>
158
  </p>
159
  <?php
160
 
161
+ if ( $home_id && $this->get_post_meta_item( '_genesis_description', $home_id ) ) {
162
  $this->description(
163
+ __( 'Note: The description placeholder is fetched from the Page SEO Settings on the homepage.', 'autodescription' )
164
  );
165
  }
166
 
167
  /**
168
  * @since 2.8.0
169
+ * @param bool $warn Whether to warn that there's a plugin active with multiple homepages.
170
  */
171
+ if ( $home_id && apply_filters( 'the_seo_framework_warn_homepage_global_description', false ) ) {
172
  $this->attention_noesc(
173
  //* Markdown escapes.
174
  $this->convert_markdown(
175
  sprintf(
176
+ /* translators: %s = Homepage URL markdown */
177
+ esc_html__( 'A plugin has been detected that suggests to maintain this option on the [homepage](%s).', 'autodescription' ),
178
  esc_url( admin_url( 'post.php?post=' . $home_id . '&action=edit#tsf-inpost-box' ) )
179
  ),
180
  [ 'a' ],
185
  break;
186
 
187
  case 'the_seo_framework_homepage_metabox_additions':
188
+ $tagline_placeholder = $this->s_title_raw( $this->get_blogdescription() );
189
+
190
  //* Fetches escaped title parts.
 
191
  $_example_title = $this->escape_title(
192
+ $this->get_filtered_raw_custom_field_title( $_generator_args ) ?: $this->get_filtered_raw_generated_title( $_generator_args )
 
193
  );
194
  // FIXME? When no blog description or tagline is set... this will be empty and ugly on no-JS.
195
  $_example_blogname = $this->escape_title( $this->get_home_page_tagline() ?: $this->get_static_untitled_title() );
196
  $_example_separator = esc_html( $this->get_separator( 'title' ) );
197
 
198
+ // TODO very readable.
199
+ $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>';
200
+ $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>';
201
 
202
  ?>
203
+
204
+ <p>
205
+ <label for="<?php $this->field_id( 'homepage_title_tagline' ); ?>" class="tsf-toblock">
206
+ <strong><?php esc_html_e( 'Meta Title Additions', 'autodescription' ); ?></strong>
207
+ </label>
208
+ </p>
209
+ <p>
210
+ <input type="text" name="<?php $this->field_name( 'homepage_title_tagline' ); ?>" class="large-text" id="<?php $this->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 />
211
+ </p>
212
+
213
+ <div id="tsf-title-tagline-toggle">
214
+ <?php
215
+ $this->wrap_fields(
216
+ $this->make_checkbox(
217
+ 'homepage_tagline',
218
+ esc_html__( 'Add Meta Title Additions to the homepage title?', 'autodescription' ),
219
+ '',
220
+ false
221
+ ),
222
+ true
223
+ );
224
+ ?>
225
+ </div>
226
+
227
+ <hr>
228
+
229
  <fieldset>
230
  <legend>
231
  <h4><?php esc_html_e( 'Meta Title Additions Location', 'autodescription' ); ?></h4>
 
232
  </legend>
233
 
234
  <p id="tsf-home-title-location" class="tsf-fields">
237
  <label for="<?php $this->field_id( 'home_title_location_left' ); ?>">
238
  <span><?php esc_html_e( 'Left:', 'autodescription' ); ?></span>
239
  <?php
240
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- $example_left is already escaped.
241
  echo $this->code_wrap_noesc( $example_left );
242
  ?>
243
  </label>
247
  <label for="<?php $this->field_id( 'home_title_location_right' ); ?>">
248
  <span><?php esc_html_e( 'Right:', 'autodescription' ); ?></span>
249
  <?php
250
+ // phpcs:ignore, WordPress.Security.EscapeOutput -- $example_right is already escaped.
251
  echo $this->code_wrap_noesc( $example_right );
252
  ?>
253
  </label>
254
  </span>
255
  </p>
256
  </fieldset>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  <?php
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  break;
259
 
260
  case 'the_seo_framework_homepage_metabox_social':
 
 
 
 
 
261
  // Gets custom fields from page.
262
+ $custom_og_title = $home_id ? $this->get_post_meta_item( '_open_graph_title', $home_id ) : '';
263
+ $custom_og_desc = $home_id ? $this->get_post_meta_item( '_open_graph_description', $home_id ) : '';
264
+ $custom_tw_title = $home_id ? $this->get_post_meta_item( '_twitter_title', $home_id ) : '';
265
+ $custom_tw_desc = $home_id ? $this->get_post_meta_item( '_twitter_description', $home_id ) : '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
+ $social_placeholders = $this->_get_social_placeholders( $_generator_args, 'settings' );
 
268
 
269
+ ?>
270
+ <p>
271
  <label for="<?php $this->field_id( 'homepage_og_title' ); ?>" class="tsf-toblock">
272
  <strong>
273
  <?php
279
  //* Output this unconditionally, with inline CSS attached to allow reacting on settings.
280
  $this->output_character_counter_wrap( $this->get_field_id( 'homepage_og_title' ), '', (bool) $this->get_option( 'display_character_counter' ) );
281
  ?>
282
+ </p>
283
  <p>
284
+ <input type="text" name="<?php $this->field_name( 'homepage_og_title' ); ?>" class="large-text" id="<?php $this->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 />
285
  </p>
286
  <?php
287
  if ( $this->has_page_on_front() && $custom_og_title ) {
288
  $this->description(
289
+ __( 'Note: The title placeholder is fetched from the Page SEO Settings on the homepage.', 'autodescription' )
290
  );
291
  }
292
  ?>
293
 
294
+ <p>
295
  <label for="<?php $this->field_id( 'homepage_og_description' ); ?>" class="tsf-toblock">
296
  <strong>
297
  <?php
303
  //* Output this unconditionally, with inline CSS attached to allow reacting on settings.
304
  $this->output_character_counter_wrap( $this->get_field_id( 'homepage_og_description' ), '', (bool) $this->get_option( 'display_character_counter' ) );
305
  ?>
306
+ </p>
307
  <p>
308
+ <textarea name="<?php $this->field_name( 'homepage_og_description' ); ?>" class="large-text" id="<?php $this->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>
309
+ <?php $this->output_js_description_elements(); ?>
310
  </p>
311
  <?php
312
  if ( $this->has_page_on_front() && $custom_og_desc ) {
313
  $this->description(
314
+ __( 'Note: The description placeholder is fetched from the Page SEO Settings on the homepage.', 'autodescription' )
315
  );
316
  }
317
  ?>
318
  <hr>
319
 
320
+ <p>
 
 
321
  <label for="<?php $this->field_id( 'homepage_twitter_title' ); ?>" class="tsf-toblock">
322
  <strong>
323
  <?php
329
  //* Output this unconditionally, with inline CSS attached to allow reacting on settings.
330
  $this->output_character_counter_wrap( $this->get_field_id( 'homepage_twitter_title' ), '', (bool) $this->get_option( 'display_character_counter' ) );
331
  ?>
332
+ </p>
333
  <p>
334
+ <input type="text" name="<?php $this->field_name( 'homepage_twitter_title' ); ?>" class="large-text" id="<?php $this->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 />
335
  </p>
336
  <?php
337
  if ( $this->has_page_on_front() && ( $custom_og_title || $custom_tw_title ) ) {
338
  $this->description(
339
+ __( 'Note: The title placeholder is fetched from the Page SEO Settings on the homepage.', 'autodescription' )
340
  );
341
  }
342
  ?>
343
 
344
+ <p>
345
  <label for="<?php $this->field_id( 'homepage_twitter_description' ); ?>" class="tsf-toblock">
346
  <strong>
347
  <?php
353
  //* Output this unconditionally, with inline CSS attached to allow reacting on settings.
354
  $this->output_character_counter_wrap( $this->get_field_id( 'homepage_twitter_description' ), '', (bool) $this->get_option( 'display_character_counter' ) );
355
  ?>
356
+ </p>
357
  <p>
358
+ <textarea name="<?php $this->field_name( 'homepage_twitter_description' ); ?>" class="large-text" id="<?php $this->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>
359
+ <?php $this->output_js_description_elements(); ?>
360
  </p>
361
  <?php
362
  if ( $this->has_page_on_front() && ( $custom_og_desc || $custom_tw_desc ) ) {
363
  $this->description(
364
+ __( 'Note: The description placeholder is fetched from the Page SEO Settings on the homepage.', 'autodescription' )
365
  );
366
  }
367
  ?>
371
  <?php
372
  $this->description( __( 'A social image can be displayed when your homepage is shared. It is a great way to grab attention.', 'autodescription' ) );
373
 
374
+ //* Fetch image placeholder.
375
+ $image_details = current( $this->get_generated_image_details( $_generator_args, true, 'social', true ) );
376
+ $image_placeholder = isset( $image_details['url'] ) ? $image_details['url'] : '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
 
378
  ?>
379
  <p>
381
  <strong><?php esc_html_e( 'Social Image URL', 'autodescription' ); ?></strong>
382
  <?php
383
  $this->make_info(
384
+ __( "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' ),
385
+ 'https://developers.facebook.com/docs/sharing/best-practices#images'
386
  );
387
  ?>
388
  </label>
391
  <input class="large-text" type="url" name="<?php $this->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' ) ); ?>" />
392
  <input type="hidden" name="<?php $this->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" />
393
  </p>
394
+ <p class="hide-if-no-tsf-js">
395
  <?php
 
396
  echo $this->get_social_image_uploader_form( 'tsf_homepage_socialimage' );
397
  ?>
398
  </p>
399
  <?php
400
  break;
401
 
402
+ case 'the_seo_framework_homepage_metabox_robots':
403
+ $noindex_post = $home_id ? $this->get_post_meta_item( '_genesis_noindex', $home_id ) : '';
404
+ $nofollow_post = $home_id ? $this->get_post_meta_item( '_genesis_nofollow', $home_id ) : '';
405
+ $noarchive_post = $home_id ? $this->get_post_meta_item( '_genesis_noarchive', $home_id ) : '';
406
+
407
+ $checked_home = '';
408
+ /**
409
+ * Shows user that the setting is checked on the homepage.
410
+ * Adds starting - with space to maintain readability.
411
+ *
412
+ * @since 2.2.4
413
+ */
414
+ if ( $noindex_post || $nofollow_post || $noarchive_post ) {
415
+ $checked_home = sprintf(
416
+ '- %s',
417
+ vsprintf(
418
+ '<a href="%s" title="%s" target=_blank class=attention>%s</a>',
419
+ [
420
+ esc_url( admin_url( 'post.php?post=' . $home_id . '&action=edit#tsf-inpost-box' ) ),
421
+ esc_attr__( 'Edit homepage page settings', 'autodescription' ),
422
+ esc_html__( 'Overwritten by page settings', 'autodescription' ),
423
+ ]
424
+ )
425
+ );
426
+ }
427
+
428
+ ?>
429
+ <h4><?php esc_html_e( 'Robots Meta Settings', 'autodescription' ); ?></h4>
430
+ <?php
431
+
432
+ $i_label = sprintf(
433
+ /* translators: 1: Option label, 2: [?] option info note, 3: Optional warning */
434
+ esc_html__( '%1$s %2$s %3$s', 'autodescription' ),
435
+ $this->convert_markdown(
436
+ /* translators: the backticks are Markdown! Preserve them as-is! */
437
+ esc_html__( 'Apply `noindex` to the homepage?', 'autodescription' ),
438
+ [ 'code' ]
439
+ ),
440
+ $this->make_info(
441
+ __( 'This tells search engines not to show this page in their search results.', 'autodescription' ),
442
+ 'https://support.google.com/webmasters/answer/93710',
443
+ false
444
+ ),
445
+ $noindex_post ? $checked_home : ''
446
+ );
447
+
448
+ $f_label = sprintf(
449
+ /* translators: 1: Option label, 2: [?] option info note, 3: Optional warning */
450
+ esc_html__( '%1$s %2$s %3$s', 'autodescription' ),
451
+ $this->convert_markdown(
452
+ /* translators: the backticks are Markdown! Preserve them as-is! */
453
+ esc_html__( 'Apply `nofollow` to the homepage?', 'autodescription' ),
454
+ [ 'code' ]
455
+ ),
456
+ $this->make_info(
457
+ __( 'This tells search engines not to follow links on this page.', 'autodescription' ),
458
+ 'https://support.google.com/webmasters/answer/96569',
459
+ false
460
+ ),
461
+ $nofollow_post ? $checked_home : ''
462
+ );
463
+
464
+ $a_label = sprintf(
465
+ /* translators: 1: Option label, 2: [?] option info note, 3: Optional warning */
466
+ esc_html__( '%1$s %2$s %3$s', 'autodescription' ),
467
+ $this->convert_markdown(
468
+ /* translators: the backtic