The SEO Framework - Version 2.9.0

Version Description

  • Equitable Erudition =

Courage is found in unlikely places - J.R.R. Tolkien

Release date:

  • 25th March 2017

Summarized:

  • This update focuses on fixing bugs and expanding the API for developers.
  • To expand the API for per page (in-post) SEO settings, tabs had to be added in the SEO settings metabox.
  • The JavaScript code has also been overhauled to improve performance and allow third party implementation.
  • Moreover, the home page settings now allow you to upload a social image.
  • Structured data markup has also been improved, like the inclusion of an alternative Sitename and fixes for Breadcrumb images.
  • The sitemap can now include the blog page. The lastmod value of which listens to multiple pages to automatically determines what's best.
  • Unfortunately, Twitter has removed support for photo cards. This update makes sure that the regarding settings correctly convert to the current format.

Only one week left for the earliest of early-bird discounts:

  • With The SEO Framework - Extension Manager's release, a celebratory discount has been introduced.
  • If you wish to benefit from this lifetime discount, visit the shop for more information.
  • More awesome extensions are coming soon!

Survey:

  • After updating, please fill in our Update Survey: tell us what you like or can be done better.

For everyone: Tabbed In-post SEO layout

  • This release brings tabbed layout to the in-post SEO settings.
  • With this change I've also altered the overall looks, with the help from Daniel.
  • This layout allows extensions from the Extension Manager to be implemented much easier.
  • This layout is self-responsive! This means it's not dependent on the browser-size, but on how much content will fit.
  • This layout therefore allows the SEO settings to be placed in the right sidebar! Take a look at the new screenshots.

For everyone: Browser support

  • This release brings CSS Flexbox to the new in-post SEO settings metabox.
  • The flexbox module is still experimental, in a way that not all browsers correctly support this to the fullest extend.
  • However, support for as many browsers feasible has been implemented. Check out the list. Enjoy!
  • The gist is that Chrome, Firefox, IE10 and Safari 6 or later are supported.

SEO Tip of the Update - Trust in layout

  • Is your website glitchy and has placeholder images, unmatched colors or weirdly contrasting borders all over the place?
  • Consider going for a more modern and clean layout. Strip out what isn't necessary. A clean layout implies professionalism and increases visitors' trust.
  • Even if you're just starting out with your website, remove all that could be but really isn't. Because if you leave in placeholders for the future, it implies amateurism.
  • People want their truth to be confirmed or their problems to be solved. If you can't confirm or solve the layout of your website, then they won't trust you to do so for their issues.
  • Simply put: It doesn't have to be perfect, it just must be done.

Detailed log:

It is some miles, but it will shorten your journey tomorrow.

Download this release

Release Info

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

Code changes from version 2.8.2 to 2.9.0

Files changed (50) hide show
  1. autodescription.php +3 -3
  2. inc/classes/admin-init.class.php +196 -15
  3. inc/classes/admin-pages.class.php +43 -3
  4. inc/classes/cache.class.php +27 -3
  5. inc/classes/core.class.php +25 -21
  6. inc/classes/debug.class.php +6 -3
  7. inc/classes/deprecated.class.php +240 -1
  8. inc/classes/detect.class.php +36 -0
  9. inc/classes/doing-it-right.class.php +42 -21
  10. inc/classes/feed.class.php +93 -52
  11. inc/classes/generate-description.class.php +47 -8
  12. inc/classes/generate-image.class.php +110 -76
  13. inc/classes/generate-ldjson.class.php +195 -82
  14. inc/classes/generate-title.class.php +2 -2
  15. inc/classes/generate-url.class.php +29 -19
  16. inc/classes/generate.class.php +57 -19
  17. inc/classes/init.class.php +60 -34
  18. inc/classes/inpost.class.php +351 -70
  19. inc/classes/load.class.php +5 -5
  20. inc/classes/metaboxes.class.php +17 -9
  21. inc/classes/post-data.class.php +2 -2
  22. inc/classes/query.class.php +64 -27
  23. inc/classes/render.class.php +5 -4
  24. inc/classes/sanitize.class.php +47 -0
  25. inc/classes/site-options.class.php +185 -209
  26. inc/classes/sitemaps.class.php +114 -24
  27. inc/classes/term-data.class.php +8 -6
  28. inc/compat/plugin-bbpress.php +173 -0
  29. inc/compat/plugin-wpmudev-dm.php +7 -0
  30. inc/functions/upgrade.php +68 -2
  31. inc/views/debug/output.php +1 -1
  32. inc/views/inpost/seo-settings-singular.php +286 -0
  33. inc/views/inpost/seo-settings-tt.php +157 -0
  34. inc/views/inpost/seo-settings.php +0 -423
  35. inc/views/metaboxes/feed-metabox.php +3 -3
  36. inc/views/metaboxes/homepage-metabox.php +64 -0
  37. inc/views/metaboxes/sitemaps-metabox.php +14 -5
  38. inc/views/metaboxes/social-metabox.php +22 -17
  39. language/autodescription.pot +336 -293
  40. lib/css/tsf-rtl.css +283 -12
  41. lib/css/tsf-rtl.min.css +1 -1
  42. lib/css/tsf.css +280 -8
  43. lib/css/tsf.min.css +1 -1
  44. lib/js/tsf.externs.js +5 -0
  45. lib/js/tsf.externs.protected.js +0 -6
  46. lib/js/tsf.js +578 -159
  47. lib/js/tsf.min.js +37 -32
  48. load.php +3 -3
  49. readme.txt +62 -160
  50. seotips/seotips.txt +8 -0
autodescription.php CHANGED
@@ -3,7 +3,7 @@
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 any WordPress website.
6
- * Version: 2.8.2
7
  * Author: Sybre Waaijer
8
  * Author URI: https://cyberwire.nl/
9
  * License: GPLv3
@@ -46,13 +46,13 @@ defined( 'ABSPATH' ) or die;
46
  * Not many caching plugins use CDN in dashboard. What a shame. Firefox does cache.
47
  * @since 1.0.0
48
  */
49
- define( 'THE_SEO_FRAMEWORK_VERSION', '2.8.2' );
50
 
51
  /**
52
  * Plugin Database version for lightweight version upgrade comparing.
53
  * @since 2.7.0
54
  */
55
- define( 'THE_SEO_FRAMEWORK_DB_VERSION', '2804' );
56
 
57
  /**
58
  * Plugin options filter.
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 any WordPress website.
6
+ * Version: 2.9.0
7
  * Author: Sybre Waaijer
8
  * Author URI: https://cyberwire.nl/
9
  * License: GPLv3
46
  * Not many caching plugins use CDN in dashboard. What a shame. Firefox does cache.
47
  * @since 1.0.0
48
  */
49
+ define( 'THE_SEO_FRAMEWORK_VERSION', '2.9.0' );
50
 
51
  /**
52
  * Plugin Database version for lightweight version upgrade comparing.
53
  * @since 2.7.0
54
  */
55
+ define( 'THE_SEO_FRAMEWORK_DB_VERSION', '2903' );
56
 
57
  /**
58
  * Plugin options filter.
inc/classes/admin-init.class.php CHANGED
@@ -210,6 +210,7 @@ class Admin_Init extends Init {
210
  * 2. Reworked output.
211
  * 3. Removed unused caching.
212
  * 4. Added dynamic output control.
 
213
  *
214
  * @return array $strings The l10n strings.
215
  */
@@ -227,10 +228,10 @@ class Admin_Init extends Init {
227
  $counter_type = (int) $this->get_user_option( 0, 'counter_type', 3 );
228
 
229
  //* Enunciate the lenghts of Titles and Descriptions.
230
- $good = __( 'Good', 'autodescription' );
231
- $okay = __( 'Okay', 'autodescription' );
232
- $bad = __( 'Bad', 'autodescription' );
233
- $unknown = __( 'Unknown', 'autodescription' );
234
 
235
  $title_separator = $this->get_separator( 'title' );
236
  $description_separator = $this->get_separator( 'description' );
@@ -305,10 +306,21 @@ class Admin_Init extends Init {
305
  $additions = $home_tagline ?: $description;
306
  }
307
 
 
308
  $nonce = \wp_create_nonce( 'autodescription-ajax-nonce' );
309
 
 
 
 
 
 
 
 
 
 
310
  return array(
311
  'nonce' => $nonce,
 
312
  'i18n' => array(
313
  'saveAlert' => \esc_html__( 'The changes you made will be lost if you navigate away from this page.', 'autodescription' ),
314
  'confirmReset' => \esc_html__( 'Are you sure you want to reset all SEO settings to their defaults?', 'autodescription' ),
@@ -323,6 +335,7 @@ class Admin_Init extends Init {
323
  'hasInput' => $this->is_term_edit() || $this->is_post_edit() || $this->is_seo_settings_page(),
324
  'counterType' => \absint( $counter_type ),
325
  'titleTagline' => $tagline,
 
326
  ),
327
  'params' => array(
328
  'siteTitle' => \esc_html( \wp_kses_decode_entities( $title ) ),
@@ -339,13 +352,13 @@ class Admin_Init extends Init {
339
  /**
340
  * Maintains and Returns additional JS l10n.
341
  *
342
- * They are put under object 'tsfemL10n.other.$key.[ $val ]'.
343
  *
344
  * @since 2.8.0
345
  * @staticvar object $strings The cached strings object.
346
  *
347
- * @param null|string $key The object key. Requires escape.
348
- * @param null|array $val The object val. Requires escape.
349
  * @param bool $get Whether to return the cached strings.
350
  * @param bool $escape Whether to escape the input.
351
  * @return object Early when $get is true
@@ -369,6 +382,73 @@ class Admin_Init extends Init {
369
  $strings->$key = $val;
370
  }
371
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  /**
373
  * CSS for the AutoDescription Bar
374
  *
@@ -442,9 +522,9 @@ class Admin_Init extends Init {
442
  *
443
  * @since 2.2.2
444
  *
445
- * @param string $page Menu slug.
446
- * @param array $query_args Optional. Associative array of query string arguments
447
- * (key => value). Default is an empty array.
448
  * @return null Return early if first argument is false.
449
  */
450
  public function admin_redirect( $page, array $query_args = array() ) {
@@ -469,17 +549,24 @@ class Admin_Init extends Init {
469
  * Handles counter option update on AJAX request.
470
  *
471
  * @since 2.6.0
 
 
472
  * @access private
473
  */
474
  public function wp_ajax_update_counter_type() {
475
 
476
- if ( $this->is_admin() && defined( 'DOING_AJAX' ) && DOING_AJAX ) {
477
 
 
478
  //* If current user isn't allowed to edit posts, don't do anything and kill PHP.
479
- if ( ! \current_user_can( 'publish_posts' ) )
480
- exit;
 
481
 
482
- \check_ajax_referer( 'autodescription-ajax-nonce', 'nonce' );
 
 
 
483
 
484
  /**
485
  * Count up, reset to 0 if needed. We have 4 options: 0, 1, 2, 3
@@ -507,6 +594,100 @@ class Admin_Init extends Init {
507
 
508
  //* Kill PHP.
509
  exit;
510
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  }
512
  }
210
  * 2. Reworked output.
211
  * 3. Removed unused caching.
212
  * 4. Added dynamic output control.
213
+ * @since 2.9.0 Added boolean $returnValue['states']['isSettingsPage']
214
  *
215
  * @return array $strings The l10n strings.
216
  */
228
  $counter_type = (int) $this->get_user_option( 0, 'counter_type', 3 );
229
 
230
  //* Enunciate the lenghts of Titles and Descriptions.
231
+ $good = \__( 'Good', 'autodescription' );
232
+ $okay = \__( 'Okay', 'autodescription' );
233
+ $bad = \__( 'Bad', 'autodescription' );
234
+ $unknown = \__( 'Unknown', 'autodescription' );
235
 
236
  $title_separator = $this->get_separator( 'title' );
237
  $description_separator = $this->get_separator( 'description' );
306
  $additions = $home_tagline ?: $description;
307
  }
308
 
309
+ //* @TODO deprecate
310
  $nonce = \wp_create_nonce( 'autodescription-ajax-nonce' );
311
 
312
+ $this->set_js_nonces( array(
313
+ /**
314
+ * Use $this->settings_capability() ?... might conflict with other nonces.
315
+ */
316
+ // 'manage_options' => \current_user_can( 'manage_options' ) ? \wp_create_nonce( 'tsf-ajax-manage_options' ) : false,
317
+ 'upload_files' => \current_user_can( 'upload_files' ) ? \wp_create_nonce( 'tsf-ajax-upload_files' ) : false,
318
+ 'edit_posts' => \current_user_can( 'edit_posts' ) ? \wp_create_nonce( 'tsf-ajax-edit_posts' ) : false,
319
+ ) );
320
+
321
  return array(
322
  'nonce' => $nonce,
323
+ 'nonces' => $this->get_js_nonces(),
324
  'i18n' => array(
325
  'saveAlert' => \esc_html__( 'The changes you made will be lost if you navigate away from this page.', 'autodescription' ),
326
  'confirmReset' => \esc_html__( 'Are you sure you want to reset all SEO settings to their defaults?', 'autodescription' ),
335
  'hasInput' => $this->is_term_edit() || $this->is_post_edit() || $this->is_seo_settings_page(),
336
  'counterType' => \absint( $counter_type ),
337
  'titleTagline' => $tagline,
338
+ 'isSettingsPage' => $this->is_seo_settings_page(),
339
  ),
340
  'params' => array(
341
  'siteTitle' => \esc_html( \wp_kses_decode_entities( $title ) ),
352
  /**
353
  * Maintains and Returns additional JS l10n.
354
  *
355
+ * They are put under object 'tsfemL10n.other[ $key ] = $val'.
356
  *
357
  * @since 2.8.0
358
  * @staticvar object $strings The cached strings object.
359
  *
360
+ * @param null|string $key The object key.
361
+ * @param array $val The object val.
362
  * @param bool $get Whether to return the cached strings.
363
  * @param bool $escape Whether to escape the input.
364
  * @return object Early when $get is true
382
  $strings->$key = $val;
383
  }
384
 
385
+ /**
386
+ * Sets up additional JS l10n values for nonces.
387
+ *
388
+ * They are put under object 'tsfemL10n.nonces[ $key ] = $val'.
389
+ *
390
+ * @since 2.9.0
391
+ *
392
+ * @param string|array $key Required. The object key or array of keys and values. Requires escape.
393
+ * @param mixed $val The object value if $key is string. Requires escape.
394
+ */
395
+ public function set_js_nonces( $key, $val = null ) {
396
+ $this->get_js_nonces( $key, $val, false );
397
+ }
398
+
399
+ /**
400
+ * Maintains and Returns additional JS l10n.
401
+ *
402
+ * They are put under object 'tsfemL10n.nonces[ $key ] = $val'.
403
+ *
404
+ * If $key is an array, $val is ignored and $key's values are used instead.
405
+ *
406
+ * @since 2.9.0
407
+ * @staticvar object $nonces The cached nonces object.
408
+ *
409
+ * @param string|array $key The object key or array of keys and values. Requires escape.
410
+ * @param mixed $val The object value if $key is string. Requires escape.
411
+ * @param bool $get Whether to return the cached nonces.
412
+ * @return object Early when $get is true
413
+ */
414
+ public function get_js_nonces( $key = null, $val = null, $get = true ) {
415
+
416
+ static $nonces = null;
417
+
418
+ if ( null === $nonces )
419
+ $nonces = new \stdClass();
420
+
421
+ if ( $get )
422
+ return $nonces;
423
+
424
+ if ( is_string( $key ) ) {
425
+ $nonces->$key = $val;
426
+ } elseif ( is_array( $key ) ) {
427
+ foreach ( $key as $k => $v ) {
428
+ $nonces->$k = $v;
429
+ }
430
+ }
431
+ }
432
+
433
+ /**
434
+ * Checks ajax referred set by set_js_nonces based on capability.
435
+ *
436
+ * Performs die() on fail.
437
+ *
438
+ * @since 2.9.0
439
+ * @access private
440
+ * It uses an internally and manually created prefix.
441
+ * @uses WP Core check_ajax_referer()
442
+ * @see @link https://developer.wordpress.org/reference/functions/check_ajax_referer/
443
+ *
444
+ * @return false|int False if the nonce is invalid, 1 if the nonce is valid
445
+ * and generated between 0-12 hours ago, 2 if the nonce is
446
+ * valid and generated between 12-24 hours ago.
447
+ */
448
+ public function check_tsf_ajax_referer( $capability ) {
449
+ return \check_ajax_referer( 'tsf-ajax-' . $capability, 'nonce' );
450
+ }
451
+
452
  /**
453
  * CSS for the AutoDescription Bar
454
  *
522
  *
523
  * @since 2.2.2
524
  *
525
+ * @param string $page Menu slug.
526
+ * @param array $query_args Optional. Associative array of query string arguments
527
+ * (key => value). Default is an empty array.
528
  * @return null Return early if first argument is false.
529
  */
530
  public function admin_redirect( $page, array $query_args = array() ) {
549
  * Handles counter option update on AJAX request.
550
  *
551
  * @since 2.6.0
552
+ * @since 2.9.0 : 1. Changed capability from 'publish_posts' to 'edit_posts'.
553
+ * 2. Added json header.
554
  * @access private
555
  */
556
  public function wp_ajax_update_counter_type() {
557
 
558
+ if ( $this->is_admin() && $this->doing_ajax() ) :
559
 
560
+ $this->check_tsf_ajax_referer( 'edit_posts' );
561
  //* If current user isn't allowed to edit posts, don't do anything and kill PHP.
562
+ if ( ! \current_user_can( 'edit_posts' ) ) {
563
+ //* Remove output buffer.
564
+ $this->clean_reponse_header();
565
 
566
+ //* Encode and echo results. Requires JSON decode within JS.
567
+ echo json_encode( array( 'type' => 'failure', 'value' => '' ) );
568
+ exit;
569
+ }
570
 
571
  /**
572
  * Count up, reset to 0 if needed. We have 4 options: 0, 1, 2, 3
594
 
595
  //* Kill PHP.
596
  exit;
597
+ endif;
598
+ }
599
+
600
+ /**
601
+ * Handles cropping of images on AJAX request.
602
+ *
603
+ * Copied from WordPress Core wp_ajax_crop_image.
604
+ * Adjusted: 1. It accepts capability 'upload_files', instead of 'customize'.
605
+ * 2. It now only accepts TSF own AJAX nonces.
606
+ * 3. It now only accepts context 'tsf-image'
607
+ * 4. It no longer accepts a default context.
608
+ *
609
+ * @since 2.9.0
610
+ * @access private
611
+ */
612
+ public function wp_ajax_crop_image() {
613
+
614
+ $this->check_tsf_ajax_referer( 'upload_files' );
615
+ if ( ! \current_user_can( 'upload_files' ) )
616
+ \wp_send_json_error();
617
+
618
+ $attachment_id = \absint( $_POST['id'] );
619
+
620
+ $context = str_replace( '_', '-', $_POST['context'] );
621
+ $data = array_map( 'absint', $_POST['cropDetails'] );
622
+ $cropped = \wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
623
+
624
+ if ( ! $cropped || \is_wp_error( $cropped ) )
625
+ \wp_send_json_error( array( 'message' => \esc_js__( 'Image could not be processed.', 'autodescription' ) ) );
626
+
627
+ switch ( $context ) :
628
+ case 'tsf-image':
629
+
630
+ /**
631
+ * Fires before a cropped image is saved.
632
+ *
633
+ * Allows to add filters to modify the way a cropped image is saved.
634
+ *
635
+ * @since 4.3.0 WordPress Core
636
+ *
637
+ * @param string $context The Customizer control requesting the cropped image.
638
+ * @param int $attachment_id The attachment ID of the original image.
639
+ * @param string $cropped Path to the cropped image file.
640
+ */
641
+ \do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped );
642
+
643
+ /** This filter is documented in wp-admin/custom-header.php */
644
+ $cropped = \apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
645
+
646
+ $parent_url = \wp_get_attachment_url( $attachment_id );
647
+ $url = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
648
+
649
+ $size = @getimagesize( $cropped );
650
+ $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
651
+
652
+ $object = array(
653
+ 'post_title' => basename( $cropped ),
654
+ 'post_content' => $url,
655
+ 'post_mime_type' => $image_type,
656
+ 'guid' => $url,
657
+ 'context' => $context,
658
+ );
659
+
660
+ $attachment_id = \wp_insert_attachment( $object, $cropped );
661
+ $metadata = \wp_generate_attachment_metadata( $attachment_id, $cropped );
662
+
663
+ /**
664
+ * Filters the cropped image attachment metadata.
665
+ *
666
+ * @since 4.3.0 WordPress Core
667
+ *
668
+ * @see wp_generate_attachment_metadata()
669
+ *
670
+ * @param array $metadata Attachment metadata.
671
+ */
672
+ $metadata = \apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata );
673
+ \wp_update_attachment_metadata( $attachment_id, $metadata );
674
+
675
+ /**
676
+ * Filters the attachment ID for a cropped image.
677
+ *
678
+ * @since 4.3.0 WordPress Core
679
+ *
680
+ * @param int $attachment_id The attachment ID of the cropped image.
681
+ * @param string $context The Customizer control requesting the cropped image.
682
+ */
683
+ $attachment_id = \apply_filters( 'wp_ajax_cropped_attachment_id', $attachment_id, $context );
684
+ break;
685
+
686
+ default :
687
+ \wp_send_json_error( array( 'message' => \esc_js__( 'Image could not be processed.', 'autodescription' ) ) );
688
+ break;
689
+ endswitch;
690
+
691
+ \wp_send_json_success( \wp_prepare_attachment_for_js( $attachment_id ) );
692
  }
693
  }
inc/classes/admin-pages.class.php CHANGED
@@ -344,6 +344,7 @@ class Admin_Pages extends Inpost {
344
  * Create a 'settings_boxes' method to add metaboxes.
345
  *
346
  * @since 2.2.2
 
347
  */
348
  public function admin() {
349
 
@@ -394,6 +395,7 @@ class Admin_Pages extends Inpost {
394
  * Display notices on the save or reset of settings.
395
  *
396
  * @since 2.2.2
 
397
  *
398
  * @return void
399
  */
@@ -463,8 +465,8 @@ class Admin_Pages extends Inpost {
463
  * @since 2.2.2
464
  * @uses $this->get_field_id() Constructs id attributes for use in form fields.
465
  *
466
- * @param string $id Field id base
467
- * @param boolean $echo echo or return
468
  * @return string Full field id
469
  */
470
  public function field_id( $id, $echo = true ) {
@@ -508,7 +510,7 @@ class Admin_Pages extends Inpost {
508
  * @since 2.6.0
509
  *
510
  * @param string $input The input to wrap. Should already be escaped.
511
- * @param bool $echo Whether to echo or return.
512
  * @return Wrapped $input.
513
  */
514
  public function wrap_fields( $input = '', $echo = false ) {
@@ -815,4 +817,42 @@ class Admin_Pages extends Inpost {
815
  return $class;
816
  }
817
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
818
  }
344
  * Create a 'settings_boxes' method to add metaboxes.
345
  *
346
  * @since 2.2.2
347
+ * @todo deprecate (method name is arbitrary) and convert to view.
348
  */
349
  public function admin() {
350
 
395
  * Display notices on the save or reset of settings.
396
  *
397
  * @since 2.2.2
398
+ * @todo convert the "request" into secure "error_notice" option. See TSF Extension Manager.
399
  *
400
  * @return void
401
  */
465
  * @since 2.2.2
466
  * @uses $this->get_field_id() Constructs id attributes for use in form fields.
467
  *
468
+ * @param string $id Field id base.
469
+ * @param boolean $echo Whether to escape echo or just return.
470
  * @return string Full field id
471
  */
472
  public function field_id( $id, $echo = true ) {
510
  * @since 2.6.0
511
  *
512
  * @param string $input The input to wrap. Should already be escaped.
513
+ * @param boolean $echo Whether to escape echo or just return.
514
  * @return Wrapped $input.
515
  */
516
  public function wrap_fields( $input = '', $echo = false ) {
817
  return $class;
818
  }
819
  }
820
+
821
+ /**
822
+ * Returns social image uploader form button.
823
+ * Also registers additional i18n strings for JS.
824
+ *
825
+ * @since 2.8.0
826
+ * @todo optimize? Sanitation and translations are duplicated -> microseconds...
827
+ *
828
+ * @param string $input_id Required. The HTML input id to pass URL into.
829
+ * @return string The image uploader button.
830
+ */
831
+ public function get_social_image_uploader_form( $input_id ) {
832
+
833
+ if ( ! $input_id )
834
+ return '';
835
+
836
+ $content = sprintf( '<a href="%1$s" class="tsf-set-social-image button button-primary button-small" title="%2$s" id="%3$s-select" data-inputid="%3$s">%4$s</a>',
837
+ \esc_url( \get_upload_iframe_src( 'image', $this->get_the_real_ID() ) ),
838
+ \esc_attr_x( 'Select social image', 'Button hover', 'autodescription' ),
839
+ \esc_attr( $input_id ),
840
+ \esc_html__( 'Select Image', 'autodescription' )
841
+ );
842
+
843
+ $button_labels = array(
844
+ 'select' => \esc_attr__( 'Select Image', 'autodescription' ),
845
+ 'select_title' => \esc_attr_x( 'Select social image', 'Button hover', 'autodescription' ),
846
+ 'change' => \esc_attr__( 'Change Image', 'autodescription' ),
847
+ 'remove' => \esc_attr__( 'Remove Image', 'autodescription' ),
848
+ 'remove_title' => \esc_attr__( 'Remove selected social image', 'autodescription' ),
849
+ 'frame_title' => \esc_attr_x( 'Select Social Image', 'Frame title', 'autodescription' ),
850
+ 'frame_button' => \esc_attr__( 'Use this image', 'autodescription' ),
851
+ );
852
+
853
+ //* Already escaped. Turn off escaping.
854
+ $this->additional_js_l10n( \esc_attr( $input_id ), $button_labels, false, false );
855
+
856
+ return $content;
857
+ }
858
  }
inc/classes/cache.class.php CHANGED
@@ -155,11 +155,14 @@ class Cache extends Sitemaps {
155
  * Flushes front-page and global transients that can be affected by options.
156
  *
157
  * @since 2.8.0
 
 
158
  */
159
  public function delete_main_cache() {
160
  $this->delete_cache( 'front' );
161
  $this->delete_cache( 'sitemap' );
162
  $this->delete_cache( 'robots' );
 
163
  }
164
 
165
  /**
@@ -191,11 +194,25 @@ class Cache extends Sitemaps {
191
  * @since 2.8.0
192
  *
193
  * @param int $user_id The User ID that has been updated.
 
194
  */
195
  public function delete_author_cache( $user_id ) {
196
  return $this->delete_cache( 'author', $user_id );
197
  }
198
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  /**
200
  * Handles all kinds of cache for removal.
201
  * Main cache deletion function handler.
@@ -275,9 +292,14 @@ class Cache extends Sitemaps {
275
  /**
276
  * Flush whole object cache group.
277
  * Set here for external functions to use. It works because of magic methods.
 
 
 
 
278
  */
279
  case 'objectflush' :
280
- if ( $this->use_object_cache ) {
 
281
  if ( isset( $GLOBALS['wp_object_cache']->cache['the_seo_framework'] ) ) {
282
  $_cache = $GLOBALS['wp_object_cache']->cache;
283
  unset( $_cache['the_seo_framework'] );
@@ -560,7 +582,7 @@ class Cache extends Sitemaps {
560
 
561
  if ( $this->is_404() ) {
562
  $the_id = '_404_';
563
- } elseif ( ( $this->is_front_page( $page_id ) ) || ( $this->is_admin() && $this->is_seo_settings_page( true ) ) ) {
564
  //* Front/HomePage.
565
  $the_id = $this->generate_front_page_cache_key();
566
  } elseif ( $this->is_blog_page( $page_id ) ) {
@@ -838,7 +860,7 @@ class Cache extends Sitemaps {
838
 
839
  if ( isset( $_POST['permalink_structure'] ) || isset( $_POST['category_base'] ) ) {
840
 
841
- if ( check_admin_referer( 'update-permalink' ) )
842
  return $this->delete_cache( 'sitemap' );
843
  }
844
 
@@ -965,6 +987,8 @@ class Cache extends Sitemaps {
965
  *
966
  * @since 2.5.2
967
  * @since 2.7.0 : Will always set "doing it wrong" transient, even if it was "doing it right" earlier.
 
 
968
  *
969
  * @NOTE: Ignores transient debug constant and options.
970
  *
155
  * Flushes front-page and global transients that can be affected by options.
156
  *
157
  * @since 2.8.0
158
+ * @since 2.9.0 : Added object cache flush.
159
+ * @TODO make 2.9 note work.
160
  */
161
  public function delete_main_cache() {
162
  $this->delete_cache( 'front' );
163
  $this->delete_cache( 'sitemap' );
164
  $this->delete_cache( 'robots' );
165
+ // $this->delete_cache( 'objectflush' );
166
  }
167
 
168
  /**
194
  * @since 2.8.0
195
  *
196
  * @param int $user_id The User ID that has been updated.
197
+ * @return bool True on success, false on failure.
198
  */
199
  public function delete_author_cache( $user_id ) {
200
  return $this->delete_cache( 'author', $user_id );
201
  }
202
 
203
+ /**
204
+ * Deletes object cache.
205
+ *
206
+ * @since 2.9.0
207
+ * @TODO make this work.
208
+ *
209
+ * @return bool True on success, false on failure.
210
+ */
211
+ public function delete_object_cache() {
212
+ return false;
213
+ // return $this->delete_cache( 'objectflush' );
214
+ }
215
+
216
  /**
217
  * Handles all kinds of cache for removal.
218
  * Main cache deletion function handler.
292
  /**
293
  * Flush whole object cache group.
294
  * Set here for external functions to use. It works because of magic methods.
295
+ *
296
+ * @NOTE Other caching plugins can override these groups. Therefore this
297
+ * does NOT work.
298
+ * @TODO make this work.
299
  */
300
  case 'objectflush' :
301
+ //* @NOTE false can't pass.
302
+ if ( false && $this->use_object_cache ) {
303
  if ( isset( $GLOBALS['wp_object_cache']->cache['the_seo_framework'] ) ) {
304
  $_cache = $GLOBALS['wp_object_cache']->cache;
305
  unset( $_cache['the_seo_framework'] );
582
 
583
  if ( $this->is_404() ) {
584
  $the_id = '_404_';
585
+ } elseif ( ( $this->is_real_front_page() || $this->is_front_page_by_id( $page_id ) ) || ( $this->is_admin() && $this->is_seo_settings_page( true ) ) ) {
586
  //* Front/HomePage.
587
  $the_id = $this->generate_front_page_cache_key();
588
  } elseif ( $this->is_blog_page( $page_id ) ) {
860
 
861
  if ( isset( $_POST['permalink_structure'] ) || isset( $_POST['category_base'] ) ) {
862
 
863
+ if ( \check_admin_referer( 'update-permalink' ) )
864
  return $this->delete_cache( 'sitemap' );
865
  }
866
 
987
  *
988
  * @since 2.5.2
989
  * @since 2.7.0 : Will always set "doing it wrong" transient, even if it was "doing it right" earlier.
990
+ * WordPress get_all_options will prevent multiple DB writes.
991
+ * Returning false on set_transient() as it was already set to '0'.
992
  *
993
  * @NOTE: Ignores transient debug constant and options.
994
  *
inc/classes/core.class.php CHANGED
@@ -133,13 +133,17 @@ class Core {
133
  * Destroys output buffer, if any. To be used with AJAX and XML to clear any PHP errors or dumps.
134
  *
135
  * @since 2.8.0
 
136
  *
137
  * @return bool True on clear. False otherwise.
138
  */
139
  protected function clean_reponse_header() {
140
 
141
- if ( ob_get_level() && ob_get_contents() ) {
142
- ob_clean();
 
 
 
143
  return true;
144
  }
145
 
@@ -152,13 +156,12 @@ class Core {
152
  *
153
  * @since 2.7.0
154
  * @access private
 
155
  *
156
  * @param string $view The file name.
157
  * @param array $args The arguments to be supplied within the file name.
158
  * Each array key is converted to a variable with its value attached.
159
  * @param string $instance The instance suffix to call back upon.
160
- *
161
- * @credits Akismet For some code.
162
  */
163
  public function get_view( $view, array $args = array(), $instance = 'main' ) {
164
 
@@ -721,6 +724,7 @@ class Core {
721
  * Calculates the relative font color according to the background.
722
  *
723
  * @since 2.8.0
 
724
  *
725
  * @param string $hex The 3 to 6 character RGB hex. '#' prefix is supported.
726
  * @return string The hexadecimal RGB relative font color, without '#' prefix.
@@ -747,12 +751,10 @@ class Core {
747
  $rel_lum = 1 - ( $sr + $sg + $sb ) / 255;
748
 
749
  //* Convert to relative intvals between 1 and 0 for L from HSL
750
- /*
751
- $rr = $r / 255;
752
- $rg = $g / 255;
753
- $rb = $b / 255;
754
- $luminance = ( min( $rr, $rg, $rb ) + max( $rr, $rg, $rb ) ) / 2;
755
- */
756
 
757
  //* Get perceptive luminance (greyscale) according to W3C.
758
  $gr = 0.2989 * $r;
@@ -763,14 +765,14 @@ class Core {
763
  //* Invert colors if they hit luminance boundaries.
764
  if ( $rel_lum < 0.5 ) {
765
  //* Build dark. Add softness.
766
- $gr = $gr * $per_lum / 8 / 0.2989 + 8 * 0.2989;
767
- $gg = $gg * $per_lum / 8 / 0.5870 + 8 * 0.5870;
768
- $gb = $gb * $per_lum / 8 / 0.1140 + 8 * 0.1140;
769
  } else {
770
  //* Build light. Add (subtract) softness.
771
- $gr = 255 - $gr * $per_lum / 8 * 0.2989 - 8 * 0.2989;
772
- $gg = 255 - $gg * $per_lum / 8 * 0.5870 - 8 * 0.5870;
773
- $gb = 255 - $gb * $per_lum / 8 * 0.1140 - 8 * 0.1140;
774
  }
775
 
776
  //* Complete hexvals.
@@ -788,6 +790,9 @@ class Core {
788
  * Note: This code has been rightfully stolen from the Extension Manager plugin (sorry Sybre!).
789
  *
790
  * @since 2.8.0
 
 
 
791
  * @link https://wordpress.org/plugins/about/readme.txt
792
  *
793
  * @param string $text The text that might contain markdown. Expected to be escaped.
@@ -827,8 +832,7 @@ class Core {
827
  foreach ( $md_types as $type ) :
828
  switch ( $type ) :
829
  case 'strong' :
830
- //* Considers word boundary. @TODO consider removing this?
831
- $count = preg_match_all( '/(?:\*{2})\b([^\*{2}]+)(?:\*{2})/', $text, $matches, PREG_PATTERN_ORDER );
832
 
833
  for ( $i = 0; $i < $count; $i++ ) {
834
  $text = str_replace(
@@ -840,7 +844,7 @@ class Core {
840
  break;
841
 
842
  case 'em' :
843
- $count = preg_match_all( '/(?:\*{1})([^\*{1}]+)(?:\*{1})/', $text, $matches, PREG_PATTERN_ORDER );
844
 
845
  for ( $i = 0; $i < $count; $i++ ) {
846
  $text = str_replace(
@@ -852,7 +856,7 @@ class Core {
852
  break;
853
 
854
  case 'code' :
855
- $count = preg_match_all( '/(?:`{1})([^`{1}]+)(?:`{1})/', $text, $matches, PREG_PATTERN_ORDER );
856
 
857
  for ( $i = 0; $i < $count; $i++ ) {
858
  $text = str_replace(
@@ -871,7 +875,7 @@ class Core {
871
  case 'h1' :
872
  $amount = filter_var( $type, FILTER_SANITIZE_NUMBER_INT );
873
  //* Considers word non-boundary. @TODO consider removing this?
874
- $expression = "/(?:={{$amount}})\B([^={{$amount}}]+?)\B(?:={{$amount}})/";
875
  $count = preg_match_all( $expression, $text, $matches, PREG_PATTERN_ORDER );
876
 
877
  for ( $i = 0; $i < $count; $i++ ) {
133
  * Destroys output buffer, if any. To be used with AJAX and XML to clear any PHP errors or dumps.
134
  *
135
  * @since 2.8.0
136
+ * @since 2.9.0 : Now flushes all levels rather than just the latest one.
137
  *
138
  * @return bool True on clear. False otherwise.
139
  */
140
  protected function clean_reponse_header() {
141
 
142
+ if ( $level = ob_get_level() ) {
143
+ while ( $level ) {
144
+ ob_end_clean();
145
+ $level--;
146
+ }
147
  return true;
148
  }
149
 
156
  *
157
  * @since 2.7.0
158
  * @access private
159
+ * @credits Akismet For some code.
160
  *
161
  * @param string $view The file name.
162
  * @param array $args The arguments to be supplied within the file name.
163
  * Each array key is converted to a variable with its value attached.
164
  * @param string $instance The instance suffix to call back upon.
 
 
165
  */
166
  public function get_view( $view, array $args = array(), $instance = 'main' ) {
167
 
724
  * Calculates the relative font color according to the background.
725
  *
726
  * @since 2.8.0
727
+ * @since 2.9.0 Now adds a little more relative softness based on rel_lum.
728
  *
729
  * @param string $hex The 3 to 6 character RGB hex. '#' prefix is supported.
730
  * @return string The hexadecimal RGB relative font color, without '#' prefix.
751
  $rel_lum = 1 - ( $sr + $sg + $sb ) / 255;
752
 
753
  //* Convert to relative intvals between 1 and 0 for L from HSL
754
+ // $rr = $r / 255;
755
+ // $rg = $g / 255;
756
+ // $rb = $b / 255;
757
+ // $luminance = ( min( $rr, $rg, $rb ) + max( $rr, $rg, $rb ) ) / 2;
 
 
758
 
759
  //* Get perceptive luminance (greyscale) according to W3C.
760
  $gr = 0.2989 * $r;
765
  //* Invert colors if they hit luminance boundaries.
766
  if ( $rel_lum < 0.5 ) {
767
  //* Build dark. Add softness.
768
+ $gr = $gr * $per_lum / 8 / 0.2989 + 8 * 0.2989 / $rel_lum;
769
+ $gg = $gg * $per_lum / 8 / 0.5870 + 8 * 0.5870 / $rel_lum;
770
+ $gb = $gb * $per_lum / 8 / 0.1140 + 8 * 0.1140 / $rel_lum;
771
  } else {
772
  //* Build light. Add (subtract) softness.
773
+ $gr = 255 - $gr * $per_lum / 8 * 0.2989 - 8 * 0.2989 / $rel_lum;
774
+ $gg = 255 - $gg * $per_lum / 8 * 0.5870 - 8 * 0.5870 / $rel_lum;
775
+ $gb = 255 - $gb * $per_lum / 8 * 0.1140 - 8 * 0.1140 / $rel_lum;
776
  }
777
 
778
  //* Complete hexvals.
790
  * Note: This code has been rightfully stolen from the Extension Manager plugin (sorry Sybre!).
791
  *
792
  * @since 2.8.0
793
+ * @since 2.9.0 : 1. Removed word boundary requirement for strong.
794
+ * 2. Now accepts regex count their numeric values in string.
795
+ * 3. Fixed header 1~6 calculation.
796
  * @link https://wordpress.org/plugins/about/readme.txt
797
  *
798
  * @param string $text The text that might contain markdown. Expected to be escaped.
832
  foreach ( $md_types as $type ) :
833
  switch ( $type ) :
834
  case 'strong' :
835
+ $count = preg_match_all( '/(?:\*{2})([^\*{\2}]+)(?:\*{2})/', $text, $matches, PREG_PATTERN_ORDER );
 
836
 
837
  for ( $i = 0; $i < $count; $i++ ) {
838
  $text = str_replace(
844
  break;
845
 
846
  case 'em' :
847
+ $count = preg_match_all( '/(?:\*{1})([^\*{\1}]+)(?:\*{1})/', $text, $matches, PREG_PATTERN_ORDER );
848
 
849
  for ( $i = 0; $i < $count; $i++ ) {
850
  $text = str_replace(
856
  break;
857
 
858
  case 'code' :
859
+ $count = preg_match_all( '/(?:`{1})([^`{\1}]+)(?:`{1})/', $text, $matches, PREG_PATTERN_ORDER );
860
 
861
  for ( $i = 0; $i < $count; $i++ ) {
862
  $text = str_replace(
875
  case 'h1' :
876
  $amount = filter_var( $type, FILTER_SANITIZE_NUMBER_INT );
877
  //* Considers word non-boundary. @TODO consider removing this?
878
+ $expression = sprintf( '/(?:\={%1$s})\B([^\={\%1$s}]+)\B(?:\={%1$s})/', $amount );
879
  $count = preg_match_all( $expression, $text, $matches, PREG_PATTERN_ORDER );
880
 
881
  for ( $i = 0; $i < $count; $i++ ) {
inc/classes/debug.class.php CHANGED
@@ -895,7 +895,8 @@ final class Debug implements Debug_Interface {
895
  //* Don't register this output.
896
  $this->add_debug_output = false;
897
 
898
- $output = $tsf->the_description()
 
899
  . $tsf->og_image()
900
  . $tsf->og_locale()
901
  . $tsf->og_type()
@@ -925,7 +926,7 @@ final class Debug implements Debug_Interface {
925
 
926
  $timer = '<div style="display:inline-block;width:100%;padding:20px;border-bottom:1px solid #ccc;">Generated in: ' . number_format( $this->timer(), 5 ) . ' seconds</div>' ;
927
 
928
- $title = $tsf->is_admin() ? 'Expected SEO Output' : 'Current SEO Output';
929
  $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>';
930
 
931
  //* Escape it, replace EOL with breaks, and style everything between quotes (which are ending with space).
@@ -1020,6 +1021,7 @@ final class Debug implements Debug_Interface {
1020
  $tsf = \the_seo_framework();
1021
 
1022
  //* Only get true/false values.
 
1023
  $is_404 = $tsf->is_404();
1024
  $is_admin = $tsf->is_admin();
1025
  $is_attachment = $tsf->is_attachment();
@@ -1034,7 +1036,8 @@ final class Debug implements Debug_Interface {
1034
  $is_date = $tsf->is_date();
1035
  $is_day = $tsf->is_day();
1036
  $is_feed = $tsf->is_feed();
1037
- $is_front_page = $tsf->is_front_page();
 
1038
  $is_home = $tsf->is_home();
1039
  $is_month = $tsf->is_month();
1040
  $is_page = $tsf->is_page();
895
  //* Don't register this output.
896
  $this->add_debug_output = false;
897
 
898
+ $output = $tsf->robots()
899
+ . $tsf->the_description()
900
  . $tsf->og_image()
901
  . $tsf->og_locale()
902
  . $tsf->og_type()
926
 
927
  $timer = '<div style="display:inline-block;width:100%;padding:20px;border-bottom:1px solid #ccc;">Generated in: ' . number_format( $this->timer(), 5 ) . ' seconds</div>' ;
928
 
929
+ $title = $tsf->is_admin() ? 'Expected SEO Output' : 'Determined SEO Output';
930
  $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>';
931
 
932
  //* Escape it, replace EOL with breaks, and style everything between quotes (which are ending with space).
1021
  $tsf = \the_seo_framework();
1022
 
1023
  //* Only get true/false values.
1024
+ $page_id = $tsf->get_the_real_ID();
1025
  $is_404 = $tsf->is_404();
1026
  $is_admin = $tsf->is_admin();
1027
  $is_attachment = $tsf->is_attachment();
1036
  $is_date = $tsf->is_date();
1037
  $is_day = $tsf->is_day();
1038
  $is_feed = $tsf->is_feed();
1039
+ $is_real_front_page = $tsf->is_real_front_page();
1040
+ $is_front_page_by_id = $tsf->is_front_page_by_id( $tsf->get_the_real_ID() );
1041
  $is_home = $tsf->is_home();
1042
  $is_month = $tsf->is_month();
1043
  $is_page = $tsf->is_page();
inc/classes/deprecated.class.php CHANGED
@@ -97,7 +97,7 @@ final class Deprecated {
97
 
98
  \the_seo_framework()->_deprecated_function( 'The_SEO_Framework_Transients::' . __FUNCTION__, '2.7.0', 'The_SEO_Framework_Transients::delete_auto_description_frontpage_transient()' );
99
 
100
- \the_seo_framework()->delete_auto_description_transient( the_seo_framework()->get_the_front_page_ID(), '', 'frontpage' );
101
 
102
  return $old_option;
103
  }
@@ -502,4 +502,243 @@ final class Deprecated {
502
 
503
  return $data;
504
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  }
97
 
98
  \the_seo_framework()->_deprecated_function( 'The_SEO_Framework_Transients::' . __FUNCTION__, '2.7.0', 'The_SEO_Framework_Transients::delete_auto_description_frontpage_transient()' );
99
 
100
+ \the_seo_framework()->delete_auto_description_transient( \the_seo_framework()->get_the_front_page_ID(), '', 'frontpage' );
101
 
102
  return $old_option;
103
  }
502
 
503
  return $data;
504
  }
505
+
506
+ /**
507
+ * Fetches og:image URL.
508
+ *
509
+ * @since 2.2.2
510
+ * @since 2.2.8 : Added theme icon detection.
511
+ * @since 2.5.2 : Added args filters.
512
+ * @since 2.8.0 : 1. Added theme logo detection.
513
+ * 2. Added inpost image selection detection.
514
+ * @since 2.8.2 : 1. Now returns something on post ID 0.
515
+ * 2. Added SEO settings fallback image selection detection.
516
+ * @since 2.9.0 : 1. Added 'skip_fallback' option to arguments.
517
+ * 2. Added 'escape' option to arguments.
518
+ * 3. First parameter is now arguments. Fallback for integer is added.
519
+ * 4. Second parameter is now deprecated.
520
+ * 5. Deprecated.
521
+ * @deprecated Use get_social_image instead.
522
+ *
523
+ * @param int|array $args The image arguments.
524
+ * Was: $post_id.
525
+ * Warning: Integer usage is only used for backwards compat.
526
+ * @param array $depr_args, Deprecated;
527
+ * Was $args The image arguments.
528
+ * @param bool $escape Whether to escape the image URL.
529
+ * Deprecated: You should use $args['escape'].
530
+ * @return string the image URL.
531
+ */
532
+ public function get_image( $args = array(), $depr_args = '', $depr_escape = true ) {
533
+
534
+ $tsf = \the_seo_framework();
535
+
536
+ $tsf->_deprecated_function( 'the_seo_framework()->get_image()', '2.9.0', 'the_seo_framework()->get_social_image()' );
537
+
538
+ if ( is_int( $args ) || is_array( $depr_args ) ) {
539
+ $tsf->_doing_it_wrong( __METHOD__, 'First parameter is now used for arguments. Second parameter is deprecated.', '2.9.0' );
540
+
541
+ $post_id = $args;
542
+ $args = array();
543
+
544
+ /**
545
+ * Backwards compat with parse args.
546
+ * @since 2.5.0
547
+ */
548
+ if ( ! isset( $depr_args['post_id'] ) ) {
549
+ $args['post_id'] = $post_id ?: ( $tsf->is_singular( $post_id ) ? $tsf->get_the_real_ID() : 0 );
550
+ }
551
+
552
+ if ( is_array( $depr_args ) ) {
553
+ $args = \wp_parse_args( $depr_args, $args );
554
+ }
555
+ }
556
+
557
+ if ( false === $depr_escape ) {
558
+ $tsf->_doing_it_wrong( __METHOD__, 'Third parameter has been deprecated. Use `$args["escape"] => false` instead.', '2.9.0' );
559
+ $args['escape'] = false;
560
+ }
561
+
562
+ $args = $tsf->reparse_image_args( $args );
563
+
564
+ //* 0. Image from argument.
565
+ pre_0 : {
566
+ if ( $image = $args['image'] )
567
+ goto end;
568
+ }
569
+
570
+ //* Check if there are no disallowed arguments.
571
+ $all_allowed = empty( $args['disallowed'] );
572
+
573
+ //* 1. Fetch image from homepage SEO meta upload.
574
+ if ( $all_allowed || false === in_array( 'homemeta', $args['disallowed'], true ) ) {
575
+ if ( $image = $tsf->get_social_image_url_from_home_meta( $args['post_id'], true ) )
576
+ goto end;
577
+ }
578
+
579
+ if ( $args['post_id'] ) {
580
+ //* 2. Fetch image from SEO meta upload.
581
+ if ( $all_allowed || false === in_array( 'postmeta', $args['disallowed'], true ) ) {
582
+ if ( $image = $tsf->get_social_image_url_from_post_meta( $args['post_id'], true ) )
583
+ goto end;
584
+ }
585
+
586
+ //* 3. Fetch image from featured.
587
+ if ( $all_allowed || false === in_array( 'featured', $args['disallowed'], true ) ) {
588
+ if ( $image = $tsf->get_image_from_post_thumbnail( $args, true ) )
589
+ goto end;
590
+ }
591
+ }
592
+
593
+ if ( $args['skip_fallback'] )
594
+ goto end;
595
+
596
+ //* 4. Fetch image from SEO settings
597
+ if ( $all_allowed || false === in_array( 'option', $args['disallowed'], true ) ) {
598
+ if ( $image = $tsf->get_social_image_url_from_seo_settings( true ) )
599
+ goto end;
600
+ }
601
+
602
+ //* 5. Fetch image from fallback filter 1
603
+ /**
604
+ * Applies filters 'the_seo_framework_og_image_after_featured' : string
605
+ * @since 2.5.2
606
+ */
607
+ fallback_1 : {
608
+ if ( $image = (string) \apply_filters( 'the_seo_framework_og_image_after_featured', '', $args['post_id'] ) )
609
+ goto end;
610
+ }
611
+
612
+ //* 6. Fallback: Get header image if exists
613
+ if ( ( $all_allowed || false === in_array( 'header', $args['disallowed'], true ) ) && \current_theme_supports( 'custom-header', 'default-image' ) ) {
614
+ if ( $image = $tsf->get_header_image( true ) )
615
+ goto end;
616
+ }
617
+
618
+ //* 7. Fetch image from fallback filter 2
619
+ /**
620
+ * Applies filters 'the_seo_framework_og_image_after_header' : string
621
+ * @since 2.5.2
622
+ */
623
+ fallback_2 : {
624
+ if ( $image = (string) \apply_filters( 'the_seo_framework_og_image_after_header', '', $args['post_id'] ) )
625
+ goto end;
626
+ }
627
+
628
+ //* 8. Get the WP 4.5 Site Logo
629
+ if ( ( $all_allowed || false === in_array( 'logo', $args['disallowed'], true ) ) && $tsf->can_use_logo() ) {
630
+ if ( $image = $tsf->get_site_logo( true ) )
631
+ goto end;
632
+ }
633
+
634
+ //* 9. Get the WP 4.3 Site Icon
635
+ if ( $all_allowed || false === in_array( 'icon', $args['disallowed'], true ) ) {
636
+ if ( $image = $tsf->get_site_icon( 'full', true ) )
637
+ goto end;
638
+ }
639
+
640
+ end :;
641
+
642
+ if ( $args['escape'] && $image )
643
+ $image = \esc_url( $image );
644
+
645
+ return (string) $image;
646
+ }
647
+
648
+ /**
649
+ * Fetches image from post thumbnail.
650
+ * Resizes the image between 1500px if bigger. Then it saves the image and
651
+ * Keeps dimensions relative.
652
+ *
653
+ * @since 2.3.0
654
+ * @since 2.9.0 Changed parameters.
655
+ * @since 2.9.0 Deprecated.
656
+ * @deprecated
657
+ *
658
+ * @param array $args The image args.
659
+ * Was: int $id The post/page ID.
660
+ * @param bool $set_og_dimensions Whether to set Open Graph image dimensions.
661
+ * Was: array $depr_args Deprecated. Image arguments.
662
+ * @return string|null the image url.
663
+ */
664
+ public function get_image_from_post_thumbnail( $args = array(), $set_og_dimensions = false ) {
665
+
666
+ $tsf = \the_seo_framework();
667
+
668
+ $tsf->_deprecated_function( 'the_seo_framework()->get_image_from_post_thumbnail()', '2.9.0', 'the_seo_framework()->get_social_image_url_from_post_thumbnail()' );
669
+
670
+ if ( is_array( $set_og_dimensions ) ) {
671
+ $tsf->_doing_it_wrong( __METHOD__, 'First parameter are now arguments, second parameter is for setting og dimensions.', '2.9.0' );
672
+ $args = $set_og_dimensions;
673
+ $set_og_dimensions = false;
674
+ }
675
+
676
+ $args = $tsf->reparse_image_args( $args );
677
+
678
+ $id = \get_post_thumbnail_id( $args['post_id'] );
679
+
680
+ $args['get_the_real_ID'] = true;
681
+
682
+ $image = $id ? $tsf->parse_og_image( $id, $args, $set_og_dimensions ) : '';
683
+
684
+ return $image;
685
+ }
686
+
687
+ /**
688
+ * Detects front page.
689
+ *
690
+ * Returns true on SEO settings page if ID is 0.
691
+ *
692
+ * @since 2.6.0
693
+ * @since 2.9.0: Deprecated.
694
+ * @deprecated
695
+ *
696
+ * @param int $id The Page or Post ID.
697
+ * @return bool
698
+ */
699
+ public function is_front_page( $id = 0 ) {
700
+
701
+ $tsf = \the_seo_framework();
702
+
703
+ $tsf->_deprecated_function( 'the_seo_framework()->is_front_page()', '2.9.0', 'the_seo_framework()->is_real_front_page() or the_seo_framework()->is_front_page_by_id()' );
704
+
705
+ static $cache = array();
706
+
707
+ if ( null !== $cache = $tsf->get_query_cache( __METHOD__, null, $id ) )
708
+ return $cache;
709
+
710
+ $is_front_page = false;
711
+
712
+ if ( \is_front_page() && empty( $id ) )
713
+ $is_front_page = true;
714
+
715
+ //* Elegant Themes Support. Yay.
716
+ if ( false === $is_front_page && empty( $id ) && $tsf->is_home() ) {
717
+ $sof = \get_option( 'show_on_front' );
718
+
719
+ if ( 'page' !== $sof && 'posts' !== $sof )
720
+ $is_front_page = true;
721
+ }
722
+
723
+ //* Compare against $id
724
+ if ( false === $is_front_page && $id ) {
725
+ $sof = \get_option( 'show_on_front' );
726
+
727
+ if ( 'page' === $sof && (int) \get_option( 'page_on_front' ) === $id )
728
+ $is_front_page = true;
729
+
730
+ if ( 'posts' === $sof && (int) \get_option( 'page_for_posts' ) === $id )
731
+ $is_front_page = true;
732
+ } elseif ( empty( $id ) && $tsf->is_seo_settings_page() ) {
733
+ $is_front_page = true;
734
+ }
735
+
736
+ $tsf->set_query_cache(
737
+ __METHOD__,
738
+ $is_front_page,
739
+ $id
740
+ );
741
+
742
+ return $is_front_page;
743
+ }
744
  }
inc/classes/detect.class.php CHANGED
@@ -41,6 +41,23 @@ class Detect extends Render {
41
  parent::__construct();
42
  }
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  /**
45
  * Returns list of active plugins.
46
  *
@@ -500,6 +517,7 @@ class Detect extends Render {
500
  *
501
  * @since 2.6.0
502
  * @since 2.8.0 Added check_option parameter.
 
503
  *
504
  * @param bool $check_option Whether to check for sitemap option.
505
  * @return bool True when no conflicting plugins are detected or when The SEO Framework's Sitemaps are output.
@@ -520,6 +538,9 @@ class Detect extends Render {
520
  return false;
521
  }
522
 
 
 
 
523
  return true;
524
  }
525
 
@@ -1033,4 +1054,19 @@ class Detect extends Render {
1033
 
1034
  return isset( $cache ) ? $cache : $this->wp_version( '4.5.0', '>=' ) && \current_theme_supports( 'custom-logo' );
1035
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1036
  }
41
  parent::__construct();
42
  }
43
 
44
+ /**
45
+ * Determines if we're doing ajax.
46
+ *
47
+ * @since 2.9.0
48
+ * @staticvar bool $cache
49
+ *
50
+ * @return bool True if AJAX
51
+ */
52
+ public function doing_ajax() {
53
+
54
+ static $cache = null;
55
+
56
+ null === $cache and $cache = defined( 'DOING_AJAX' ) && DOING_AJAX;
57
+
58
+ return $cache;
59
+ }
60
+
61
  /**
62
  * Returns list of active plugins.
63
  *
517
  *
518
  * @since 2.6.0
519
  * @since 2.8.0 Added check_option parameter.
520
+ * @since 2.9.0 Now also checks for subdirectory installations.
521
  *
522
  * @param bool $check_option Whether to check for sitemap option.
523
  * @return bool True when no conflicting plugins are detected or when The SEO Framework's Sitemaps are output.
538
  return false;
539
  }
540
 
541
+ if ( $this->is_subdirectory_installation() )
542
+ return false;
543
+
544
  return true;
545
  }
546
 
1054
 
1055
  return isset( $cache ) ? $cache : $this->wp_version( '4.5.0', '>=' ) && \current_theme_supports( 'custom-logo' );
1056
  }
1057
+
1058
+ /**
1059
+ * Determines if the current installation is on a subdirectory.
1060
+ *
1061
+ * @since 2.9.0-
1062
+ * @staticvar $bool $cache
1063
+ *
1064
+ * @return bool
1065
+ */
1066
+ public function is_subdirectory_installation() {
1067
+
1068
+ static $cache = null;
1069
+
1070
+ return isset( $cache ) ? $cache : '' !== $this->get_home_path();
1071
+ }
1072
  }
inc/classes/doing-it-right.class.php CHANGED
@@ -95,7 +95,7 @@ class Doing_It_Right extends Generate_Ldjson {
95
  /**
96
  * Securely check the referrer, instead of leaving holes everywhere.
97
  */
98
- if ( defined( 'DOING_AJAX' ) && DOING_AJAX && \check_ajax_referer( 'add-tag', '_wpnonce_add-tag', false ) ) {
99
 
100
  $taxonomy = ! empty( $_POST['taxonomy'] ) ? $_POST['taxonomy'] : 'post_tag';
101
  $tax = \get_taxonomy( $taxonomy );
@@ -106,28 +106,38 @@ class Doing_It_Right extends Generate_Ldjson {
106
  }
107
 
108
  /**
109
- * Initializes columns
110
- *
111
- * Applies filter the_seo_framework_show_seo_column : Boolean Show the SEO column in edit.php
112
  *
113
  * @since 2.1.9
114
  *
115
  * @param object|empty $screen WP_Screen
116
  * @param bool $doing_ajax Whether we're doing an AJAX response.
 
117
  */
118
  public function init_columns( $screen = '', $doing_ajax = false ) {
119
 
 
 
 
 
 
 
 
120
  $show_seo_column = (bool) \apply_filters( 'the_seo_framework_show_seo_column', true );
121
 
 
 
 
122
  if ( $doing_ajax ) {
123
  $post_type = isset( $_POST['post_type'] ) ? $_POST['post_type'] : '';
124
  } else {
125
  $post_type = isset( $screen->post_type ) ? $screen->post_type : '';
126
  }
127
 
128
- if ( $show_seo_column && $this->post_type_supports_custom_seo( $post_type ) ) {
129
  if ( $doing_ajax ) {
130
 
 
131
  $id = isset( $_POST['screen'] ) ? $_POST['screen'] : false;
132
  $taxonomy = isset( $_POST['taxonomy'] ) ? $_POST['taxonomy'] : false;
133
 
@@ -509,7 +519,7 @@ class Doing_It_Right extends Generate_Ldjson {
509
 
510
  $data = $this->get_term_data( $term, $term->term_id );
511
 
512
- $noindex = isset( $data['noindex'] ) && $this->is_checked( $data['noindex'] );
513
  $redirect = false; // We don't apply redirect on taxonomies (yet)
514
 
515
  //* Blocked SEO, return simple bar.
@@ -549,14 +559,11 @@ class Doing_It_Right extends Generate_Ldjson {
549
  $is_term = false;
550
  $is_front_page = $this->is_static_frontpage( $post_id );
551
 
552
- $redirect = $this->get_custom_field( 'redirect', $post_id );
553
- $redirect = empty( $redirect ) ? false : true;
554
-
555
- $noindex = $this->get_custom_field( '_genesis_noindex', $post_id );
556
- $noindex = $this->is_checked( $noindex );
557
 
558
  if ( $is_front_page )
559
- $noindex = $this->is_option_checked( 'homepage_noindex' ) ? true : $noindex;
560
 
561
  if ( $redirect || $noindex )
562
  return $this->the_seo_bar_blocked( array( 'is_term' => $is_term, 'redirect' => $redirect, 'noindex' => $noindex, 'post_i18n' => $post ) );
@@ -609,6 +616,7 @@ class Doing_It_Right extends Generate_Ldjson {
609
  * Fetch the term data for The SEO Bar.
610
  *
611
  * @since 2.6.0
 
612
  * @staticvar array $data
613
  *
614
  * @param array $args The term args.
@@ -617,6 +625,7 @@ class Doing_It_Right extends Generate_Ldjson {
617
  * 'title_is_from_custom_field' => $title_is_from_custom_field,
618
  * 'description' => $description,
619
  * 'description_is_from_custom_field' => $description_is_from_custom_field,
 
620
  * 'nofollow' => $nofollow,
621
  * 'noarchive' => $noarchive
622
  * }
@@ -631,6 +640,7 @@ class Doing_It_Right extends Generate_Ldjson {
631
 
632
  $title_custom_field = isset( $data['doctitle'] ) ? $data['doctitle'] : '';
633
  $description_custom_field = isset( $data['description'] ) ? $data['description'] : '';
 
634
  $nofollow = isset( $data['nofollow'] ) ? $data['nofollow'] : '';
635
  $noarchive = isset( $data['noarchive'] ) ? $data['noarchive'] : '';
636
 
@@ -654,14 +664,19 @@ class Doing_It_Right extends Generate_Ldjson {
654
  $description = $this->generate_description( '', $description_args );
655
  }
656
 
657
- $nofollow = $this->is_checked( $nofollow );
658
- $noarchive = $this->is_checked( $noarchive );
 
 
 
 
659
 
660
  return array(
661
  'title' => $title,
662
  'title_is_from_custom_field' => $title_is_from_custom_field,
663
  'description' => $description,
664
  'description_is_from_custom_field' => $description_is_from_custom_field,
 
665
  'nofollow' => $nofollow,
666
  'noarchive' => $noarchive,
667
  );
@@ -671,6 +686,7 @@ class Doing_It_Right extends Generate_Ldjson {
671
  * Fetch the post data for The SEO Bar.
672
  *
673
  * @since 2.6.0
 
674
  * @staticvar array $data
675
  *
676
  * @param array $args The post args.
@@ -679,6 +695,7 @@ class Doing_It_Right extends Generate_Ldjson {
679
  * 'title_is_from_custom_field' => $title_is_from_custom_field,
680
  * 'description' => $description,
681
  * 'description_is_from_custom_field' => $description_is_from_custom_field,
 
682
  * 'nofollow' => $nofollow,
683
  * 'noarchive' => $noarchive
684
  * }
@@ -690,14 +707,16 @@ class Doing_It_Right extends Generate_Ldjson {
690
 
691
  $title_custom_field = $this->get_custom_field( '_genesis_title', $post_id );
692
  $description_custom_field = $this->get_custom_field( '_genesis_description', $post_id );
 
693
  $nofollow = $this->get_custom_field( '_genesis_nofollow', $post_id );
694
  $noarchive = $this->get_custom_field( '_genesis_noarchive', $post_id );
695
 
696
  if ( $page_on_front ) {
697
- $title_custom_field = $this->get_option( 'homepage_title' ) ? $this->get_option( 'homepage_title' ) : $title_custom_field;
698
- $description_custom_field = $this->get_option( 'homepage_description' ) ? $this->get_option( 'homepage_description' ) : $description_custom_field;
699
- $nofollow = $this->get_option( 'homepage_nofollow' ) ? $this->get_option( 'homepage_nofollow' ) : $nofollow;
700
- $noarchive = $this->get_option( 'homepage_noarchive' ) ? $this->get_option( 'homepage_noarchive' ) : $noarchive;
 
701
  }
702
 
703
  $title_is_from_custom_field = (bool) $title_custom_field;
@@ -714,14 +733,16 @@ class Doing_It_Right extends Generate_Ldjson {
714
  $description = $this->generate_description( '', array( 'id' => $post_id, 'get_custom_field' => false ) );
715
  }
716
 
717
- $nofollow = $this->is_checked( $nofollow );
718
- $noarchive = $this->is_checked( $noarchive );
 
719
 
720
  return array(
721
  'title' => $title,
722
  'title_is_from_custom_field' => $title_is_from_custom_field,
723
  'description' => $description,
724
  'description_is_from_custom_field' => $description_is_from_custom_field,
 
725
  'nofollow' => $nofollow,
726
  'noarchive' => $noarchive,
727
  );
@@ -1548,7 +1569,7 @@ class Doing_It_Right extends Generate_Ldjson {
1548
  '20%' => 'tsf-20',
1549
  '16%' => 'tsf-16',
1550
  '12.5%' => 'tsf-12-5',
1551
- '11%' => 'ad-11',
1552
  '10%' => 'tsf-10',
1553
  );
1554
  }
95
  /**
96
  * Securely check the referrer, instead of leaving holes everywhere.
97
  */
98
+ if ( $this->doing_ajax() && \check_ajax_referer( 'add-tag', '_wpnonce_add-tag', false ) ) {
99
 
100
  $taxonomy = ! empty( $_POST['taxonomy'] ) ? $_POST['taxonomy'] : 'post_tag';
101
  $tax = \get_taxonomy( $taxonomy );
106
  }
107
 
108
  /**
109
+ * Initializes SEO bar columns.
 
 
110
  *
111
  * @since 2.1.9
112
  *
113
  * @param object|empty $screen WP_Screen
114
  * @param bool $doing_ajax Whether we're doing an AJAX response.
115
+ * @return void If filter is set to false.
116
  */
117
  public function init_columns( $screen = '', $doing_ajax = false ) {
118
 
119
+ /**
120
+ * Applies filters 'the_seo_framework_show_seo_column' : bool
121
+ *
122
+ * @since ???
123
+ *
124
+ * @param bool $show_seo_column
125
+ */
126
  $show_seo_column = (bool) \apply_filters( 'the_seo_framework_show_seo_column', true );
127
 
128
+ if ( false === $show_seo_column )
129
+ return;
130
+
131
  if ( $doing_ajax ) {
132
  $post_type = isset( $_POST['post_type'] ) ? $_POST['post_type'] : '';
133
  } else {
134
  $post_type = isset( $screen->post_type ) ? $screen->post_type : '';
135
  }
136
 
137
+ if ( $this->post_type_supports_custom_seo( $post_type ) ) {
138
  if ( $doing_ajax ) {
139
 
140
+ //* Nonce is done in $this->init_columns_ajax()
141
  $id = isset( $_POST['screen'] ) ? $_POST['screen'] : false;
142
  $taxonomy = isset( $_POST['taxonomy'] ) ? $_POST['taxonomy'] : false;
143
 
519
 
520
  $data = $this->get_term_data( $term, $term->term_id );
521
 
522
+ $noindex = isset( $data['noindex'] ) && $data['noindex'];
523
  $redirect = false; // We don't apply redirect on taxonomies (yet)
524
 
525
  //* Blocked SEO, return simple bar.
559
  $is_term = false;
560
  $is_front_page = $this->is_static_frontpage( $post_id );
561
 
562
+ $redirect = (bool) $this->get_custom_field( 'redirect', $post_id );
563
+ $noindex = (bool) $this->get_custom_field( '_genesis_noindex', $post_id );
 
 
 
564
 
565
  if ( $is_front_page )
566
+ $noindex = $this->is_option_checked( 'homepage_noindex' ) ?: $noindex;
567
 
568
  if ( $redirect || $noindex )
569
  return $this->the_seo_bar_blocked( array( 'is_term' => $is_term, 'redirect' => $redirect, 'noindex' => $noindex, 'post_i18n' => $post ) );
616
  * Fetch the term data for The SEO Bar.
617
  *
618
  * @since 2.6.0
619
+ * @since 2.9.0 Now also returns noindex value.
620
  * @staticvar array $data
621
  *
622
  * @param array $args The term args.
625
  * 'title_is_from_custom_field' => $title_is_from_custom_field,
626
  * 'description' => $description,
627
  * 'description_is_from_custom_field' => $description_is_from_custom_field,
628
+ * 'noindex' => $noindex,
629
  * 'nofollow' => $nofollow,
630
  * 'noarchive' => $noarchive
631
  * }
640
 
641
  $title_custom_field = isset( $data['doctitle'] ) ? $data['doctitle'] : '';
642
  $description_custom_field = isset( $data['description'] ) ? $data['description'] : '';
643
+ $noindex = isset( $data['noindex'] ) ? $data['noindex'] : '';
644
  $nofollow = isset( $data['nofollow'] ) ? $data['nofollow'] : '';
645
  $noarchive = isset( $data['noarchive'] ) ? $data['noarchive'] : '';
646
 
664
  $description = $this->generate_description( '', $description_args );
665
  }
666
 
667
+ /**
668
+ * No longer uses is_checked. As it strict checks 1/0 strings.
669
+ */
670
+ $noindex = (bool) $noindex;
671
+ $nofollow = (bool) $nofollow;
672
+ $noarchive = (bool) $noarchive;
673
 
674
  return array(
675
  'title' => $title,
676
  'title_is_from_custom_field' => $title_is_from_custom_field,
677
  'description' => $description,
678
  'description_is_from_custom_field' => $description_is_from_custom_field,
679
+ 'noindex' => $noindex,
680
  'nofollow' => $nofollow,
681
  'noarchive' => $noarchive,
682
  );
686
  * Fetch the post data for The SEO Bar.
687
  *
688
  * @since 2.6.0
689
+ * @since 2.9.0 Now also returns noindex value.
690
  * @staticvar array $data
691
  *
692
  * @param array $args The post args.
695
  * 'title_is_from_custom_field' => $title_is_from_custom_field,
696
  * 'description' => $description,
697
  * 'description_is_from_custom_field' => $description_is_from_custom_field,
698
+ * 'noindex' => $noindex,
699
  * 'nofollow' => $nofollow,
700
  * 'noarchive' => $noarchive
701
  * }
707
 
708
  $title_custom_field = $this->get_custom_field( '_genesis_title', $post_id );
709
  $description_custom_field = $this->get_custom_field( '_genesis_description', $post_id );
710
+ $noindex = $this->get_custom_field( '_genesis_noindex', $post_id );
711
  $nofollow = $this->get_custom_field( '_genesis_nofollow', $post_id );
712
  $noarchive = $this->get_custom_field( '_genesis_noarchive', $post_id );
713
 
714
  if ( $page_on_front ) {
715
+ $title_custom_field = $this->get_option( 'homepage_title' ) ?: $title_custom_field;
716
+ $description_custom_field = $this->get_option( 'homepage_description' ) ?: $description_custom_field;
717
+ $noindex = $this->get_option( 'homepage_noindex' ) ?: $nofollow;
718
+ $nofollow = $this->get_option( 'homepage_nofollow' ) ?: $nofollow;
719
+ $noarchive = $this->get_option( 'homepage_noarchive' ) ?: $noarchive;
720
  }
721
 
722
  $title_is_from_custom_field = (bool) $title_custom_field;
733
  $description = $this->generate_description( '', array( 'id' => $post_id, 'get_custom_field' => false ) );
734
  }
735
 
736
+ $noindex = (bool) $noindex;
737
+ $nofollow = (bool) $nofollow;
738
+ $noarchive = (bool) $noarchive;
739
 
740
  return array(
741
  'title' => $title,
742
  'title_is_from_custom_field' => $title_is_from_custom_field,
743
  'description' => $description,
744
  'description_is_from_custom_field' => $description_is_from_custom_field,
745
+ 'noindex' => $noindex,
746
  'nofollow' => $nofollow,
747
  'noarchive' => $noarchive,
748
  );
1569
  '20%' => 'tsf-20',
1570
  '16%' => 'tsf-16',
1571
  '12.5%' => 'tsf-12-5',
1572
+ '11%' => 'tsf-11',
1573
  '10%' => 'tsf-10',
1574
  );
1575
  }
inc/classes/feed.class.php CHANGED
@@ -43,9 +43,15 @@ class Feed extends Cache {
43
  /**
44
  * Initializes feed actions and hooks.
45
  *
46
- * @since 2.6.0
 
 
 
47
  */
48
- public function init_feed() {
 
 
 
49
 
50
  \add_filter( 'the_content_feed', array( $this, 'the_content_feed' ), 10, 2 );
51
 
@@ -67,75 +73,110 @@ class Feed extends Cache {
67
  }
68
 
69
  /**
70
- * Changes feed's content.
71
  *
72
- * @param $content The feed's content.
73
- * @param $feed_type The feed type (not used in excerpted content)
74
  *
75
  * @since 2.5.2
 
 
 
 
76
  */
77
- public function the_content_feed( $content, $feed_type = null ) {
78
 
79
- if ( $content ) :
 
80
 
81
- /**
82
- * Don't alter already-excerpts or descriptions.
83
- * $feed_type is only set on 'the_content_feed' filter.
84
- */
85
- if ( isset( $feed_type ) && $this->get_option( 'excerpt_the_feed' ) ) {
86
- //* Strip all code and lines.
87
- $excerpt = $this->s_excerpt_raw( $content, false );
88
 
89
- $excerpt_len = (int) mb_strlen( $excerpt );
90
- /**
91
- * Applies filters the_seo_framework_max_content_feed_length : The max excerpt length.
92
- * @since 2.5.2
93
- */
94
- $max_len = (int) \apply_filters( 'the_seo_framework_max_content_feed_length', 400 );
95
 
96
- //* Generate excerpt.
97
- $excerpt = $this->trim_excerpt( $excerpt, $excerpt_len, $max_len );
98
 
99
- $h2_output = '';
 
 
 
 
 
 
 
 
100
 
101
- if ( 0 === strpos( $content, '<h2>' ) ) {
102
- //* Add the h2 title back
103
- $h2_end = mb_strpos( $content, '</h2>' );
104
 
105
- if ( false !== $h2_end ) {
106
- //* Start of content, plus <h2>
107
- $h2_start = 4;
108
- //* Remove the length of <h2>, again.
109
- $h2_end = $h2_end - $h2_start;
110
 
111
- //* Fetch h2 content.
112
- $h2_content = mb_substr( $content, $h2_start, $h2_end );
 
 
 
 
113
 
114
- //* Remove the H2 content from the excerpt.
115
- $count = 1;
116
- $excerpt = str_replace( $h2_content, '', $excerpt, $count );
117
 
118
- //* Wrap h2 content in h2 tags.
119
- $h2_output = '<h2>' . $h2_content . '</h2>' . PHP_EOL;
120
- }
121
- }
122
 
123
- $content = $h2_output . '<p>' . trim( $excerpt ) . '</p>';
124
- }
 
 
 
 
 
 
 
125
 
126
- if ( $this->get_option( 'source_the_feed' ) ) {
 
127
 
128
- //* Fetch permalink and add it to the content. Already escaped.
129
- $permalink = $this->the_url();
 
130
 
131
- /**
132
- * Applies filters 'the_seo_framework_feed_source_link' : string
133
- * @since 2.6.0
134
- */
135
- $source_i18n = (string) apply_filters( 'the_seo_framework_feed_source_link_text', \_x( 'Source', 'The content source', 'autodescription' ) );
136
- $content .= PHP_EOL . '<p><a href="' . $permalink . '" rel="external nofollow">' . \esc_html( $source_i18n ) . '</a></p>';
137
  }
138
- endif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
  return $content;
141
  }
43
  /**
44
  * Initializes feed actions and hooks.
45
  *
46
+ * @since 2.9.0
47
+ * @access private
48
+ *
49
+ * @return void Early if this request isn't for a feed.
50
  */
51
+ public function _init_feed_output() {
52
+
53
+ if ( ! $this->is_feed() )
54
+ return;
55
 
56
  \add_filter( 'the_content_feed', array( $this, 'the_content_feed' ), 10, 2 );
57
 
73
  }
74
 
75
  /**
76
+ * Changes feed's content based on options.
77
  *
78
+ * This method converts the input $content to an excerpt and is able to add
79
+ * a nofollow backlink at the end of the feed.
80
  *
81
  * @since 2.5.2
82
+ *
83
+ * @param $content The feed's content.
84
+ * @param $feed_type The feed type (not used in excerpted content)
85
+ * @return string The modified feed entry.
86
  */
87
+ public function the_content_feed( $content = '', $feed_type = null ) {
88
 
89
+ if ( ! $content )
90
+ return '';
91
 
92
+ /**
93
+ * Don't alter already-excerpts or descriptions.
94
+ * $feed_type is only set on 'the_content_feed' filter.
95
+ */
96
+ if ( isset( $feed_type ) && $this->get_option( 'excerpt_the_feed' ) ) {
97
+ $content = $this->convert_feed_entry_to_excerpt( $content );
98
+ }
99
 
100
+ if ( $this->get_option( 'source_the_feed' ) ) {
101
+ $content .= PHP_EOL . $this->get_feed_entry_source_link();
102
+ }
 
 
 
103
 
104
+ return $content;
105
+ }
106
 
107
+ /**
108
+ * Converts feed content to excerpt.
109
+ *
110
+ * @since 2.9.0
111
+ *
112
+ * @param string $content The full feed entry content.
113
+ * @return string The excerpted feed.
114
+ */
115
+ protected function convert_feed_entry_to_excerpt( $content = '' ) {
116
 
117
+ if ( ! $content )
118
+ return '';
 
119
 
120
+ //* Strip all code and lines.
121
+ $excerpt = $this->s_excerpt_raw( $content, false );
 
 
 
122
 
123
+ $excerpt_len = (int) mb_strlen( $excerpt );
124
+ /**
125
+ * Applies filters the_seo_framework_max_content_feed_length : The max excerpt length.
126
+ * @since 2.5.2
127
+ */
128
+ $max_len = (int) \apply_filters( 'the_seo_framework_max_content_feed_length', 400 );
129
 
130
+ //* Generate excerpt.
131
+ $excerpt = $this->trim_excerpt( $excerpt, $excerpt_len, $max_len );
 
132
 
133
+ $h2_output = '';
 
 
 
134
 
135
+ if ( 0 === strpos( $content, '<h2>' ) ) {
136
+ //* Add the h2 title back
137
+ $h2_end = mb_strpos( $content, '</h2>' );
138
+
139
+ if ( false !== $h2_end ) {
140
+ //* Start of content, plus <h2>
141
+ $h2_start = 4;
142
+ //* Remove the length of <h2>, again.
143
+ $h2_end = $h2_end - $h2_start;
144
 
145
+ //* Fetch h2 content.
146
+ $h2_content = mb_substr( $content, $h2_start, $h2_end );
147
 
148
+ //* Remove the H2 content from the excerpt.
149
+ $count = 1;
150
+ $excerpt = str_replace( $h2_content, '', $excerpt, $count );
151
 
152
+ //* Wrap h2 content in h2 tags.
153
+ $h2_output = '<h2>' . $h2_content . '</h2>' . PHP_EOL;
 
 
 
 
154
  }
155
+ }
156
+
157
+ $content = $h2_output . '<p>' . trim( $excerpt ) . '</p>';
158
+
159
+ return $content;
160
+ }
161
+
162
+ /**
163
+ * Generates and returns feed source link.
164
+ *
165
+ * @since 2.9.0
166
+ *
167
+ * @return string The translatable feed entry source link.
168
+ */
169
+ protected function get_feed_entry_source_link() {
170
+
171
+ //* Fetch permalink and add it to the content. Already escaped.
172
+ $permalink = $this->the_url();
173
+
174
+ /**
175
+ * Applies filters 'the_seo_framework_feed_source_link' : string
176
+ * @since 2.6.0
177
+ */
178
+ $source_i18n = (string) \apply_filters( 'the_seo_framework_feed_source_link_text', \_x( 'Source', 'The content source', 'autodescription' ) );
179
+ $content = '<p><a href="' . $permalink . '" rel="external nofollow">' . \esc_html( $source_i18n ) . '</a></p>';
180
 
181
  return $content;
182
  }
inc/classes/generate-description.class.php CHANGED
@@ -43,6 +43,7 @@ class Generate_Description extends Generate {
43
  * Creates description. Base function.
44
  *
45
  * @since 1.0.0
 
46
  *
47
  * @param string $description The optional description to simply parse.
48
  * @param array $args description args : {
@@ -64,16 +65,41 @@ class Generate_Description extends Generate {
64
 
65
  if ( $args['get_custom_field'] && empty( $description ) ) {
66
  //* Fetch from options, if any.
67
- $description = (string) $this->description_from_custom_field( $args, false );
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
  //* We've already checked the custom fields, so let's remove the check in the generation.
70
  $args['get_custom_field'] = false;
71
  }
72
 
73
  //* Still no description found? Create an auto description based on content.
74
- if ( empty( $description ) || false === is_scalar( $description ) )
75
  $description = $this->generate_description_from_id( $args, false );
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  /**
78
  * Applies filters 'the_seo_framework_do_shortcodes_in_description' : Boolean
79
  * @since 2.6.6
@@ -206,6 +232,8 @@ class Generate_Description extends Generate {
206
  * Fetches HomePage Description from custom field.
207
  *
208
  * @since 2.6.0
 
 
209
  * Use $this->description_from_custom_field() instead.
210
  *
211
  * @param array $args Description args.
@@ -215,9 +243,8 @@ class Generate_Description extends Generate {
215
 
216
  $description = '';
217
 
218
- if ( $args['is_home'] || $this->is_front_page() || ( empty( $args['taxonomy'] ) && $this->is_static_frontpage( $args['id'] ) ) ) {
219
  $description = $this->get_option( 'homepage_description' ) ?: '';
220
- }
221
 
222
  return $description;
223
  }
@@ -333,7 +360,7 @@ class Generate_Description extends Generate {
333
  $args = $this->reparse_description_args( $args );
334
 
335
  //* Home Page description
336
- if ( $args['is_home'] || $this->is_front_page() || $this->is_static_frontpage( $args['id'] ) )
337
  return $this->generate_home_page_description( $args['get_custom_field'], $escape );
338
 
339
  /**
@@ -638,14 +665,14 @@ class Generate_Description extends Generate {
638
  * $sep => The separator
639
  * }
640
  */
641
- public function generate_description_additions( $id = '', $term = '', $ignore = false ) {
642
 
643
  static $title = array();
644
 
645
  if ( $ignore || $this->add_description_additions( $id, $term ) ) {
646
 
647
  if ( ! isset( $title[ $id ] ) ) {
648
- $title[ $id ] = $this->generate_description_title( $id, $term, $this->is_front_page( $id ) );
649
  }
650
 
651
  if ( $ignore || $this->is_option_checked( 'description_blogname' ) ) {
@@ -695,7 +722,7 @@ class Generate_Description extends Generate {
695
  if ( '' === $id )
696
  $id = $this->get_the_real_ID();
697
 
698
- if ( $page_on_front || $this->is_static_frontpage( $id ) ) :
699
  $title = $this->get_option( 'homepage_title_tagline' ) ?: $this->get_blogdescription();
700
  else :
701
  /**
@@ -779,6 +806,18 @@ class Generate_Description extends Generate {
779
  $excerpt = '';
780
  }
781
 
 
 
 
 
 
 
 
 
 
 
 
 
782
  $excerpt_cache[ $page_id ][ $term_id ] = $excerpt;
783
  }
784
 
43
  * Creates description. Base function.
44
  *
45
  * @since 1.0.0
46
+ * @since 2.9.0 Added two filters.
47
  *
48
  * @param string $description The optional description to simply parse.
49
  * @param array $args description args : {
65
 
66
  if ( $args['get_custom_field'] && empty( $description ) ) {
67
  //* Fetch from options, if any.
68
+ $description = $this->description_from_custom_field( $args, false );
69
+
70
+ /**
71
+ * Applies filters 'the_seo_framework_custom_field_description' : string
72
+ *
73
+ * Filters the description from custom field, if any.
74
+ *
75
+ * @since 2.9.0
76
+ *
77
+ * @param string $description The description.
78
+ * @param array $args The description arguments.
79
+ */
80
+ $description = (string) \apply_filters( 'the_seo_framework_custom_field_description', $description, $args );
81
 
82
  //* We've already checked the custom fields, so let's remove the check in the generation.
83
  $args['get_custom_field'] = false;
84
  }
85
 
86
  //* Still no description found? Create an auto description based on content.
87
+ if ( empty( $description ) || false === is_scalar( $description ) ) {
88
  $description = $this->generate_description_from_id( $args, false );
89
 
90
+ /**
91
+ * Applies filters 'the_seo_framework_generated_description' : string
92
+ *
93
+ * Filters the generated description, if any.
94
+ *
95
+ * @since 2.9.0
96
+ *
97
+ * @param string $description The description.
98
+ * @param array $args The description arguments.
99
+ */
100
+ $description = (string) \apply_filters( 'the_seo_framework_generated_description', $description, $args );
101
+ }
102
+
103
  /**
104
  * Applies filters 'the_seo_framework_do_shortcodes_in_description' : Boolean
105
  * @since 2.6.6
232
  * Fetches HomePage Description from custom field.
233
  *
234
  * @since 2.6.0
235
+ * @since 2.9.0 1. Removed $args['taxonomy'] check.
236
+ * 2. Added $this->is_archive() check.
237
  * Use $this->description_from_custom_field() instead.
238
  *
239
  * @param array $args Description args.
243
 
244
  $description = '';
245
 
246
+ if ( $args['is_home'] || $this->is_real_front_page() || ( ! $this->is_archive() && $this->is_static_frontpage( $args['id'] ) ) )
247
  $description = $this->get_option( 'homepage_description' ) ?: '';
 
248
 
249
  return $description;
250
  }
360
  $args = $this->reparse_description_args( $args );
361
 
362
  //* Home Page description
363
+ if ( $args['is_home'] || $this->is_real_front_page() || $this->is_front_page_by_id( $args['id'] ) )
364
  return $this->generate_home_page_description( $args['get_custom_field'], $escape );
365
 
366
  /**
665
  * $sep => The separator
666
  * }
667
  */
668
+ public function generate_description_additions( $id = 0, $term = '', $ignore = false ) {
669
 
670
  static $title = array();
671
 
672
  if ( $ignore || $this->add_description_additions( $id, $term ) ) {
673
 
674
  if ( ! isset( $title[ $id ] ) ) {
675
+ $title[ $id ] = $this->generate_description_title( $id, $term, $this->is_real_front_page() );
676
  }
677
 
678
  if ( $ignore || $this->is_option_checked( 'description_blogname' ) ) {
722
  if ( '' === $id )
723
  $id = $this->get_the_real_ID();
724
 
725
+ if ( $page_on_front || $this->is_front_page_by_id( $id ) ) :
726
  $title = $this->get_option( 'homepage_title_tagline' ) ?: $this->get_blogdescription();
727
  else :
728
  /**
806
  $excerpt = '';
807
  }
808
 
809
+ /**
810
+ * Applies filters 'the_seo_framework_fetched_description_excerpt' : string
811
+ *
812
+ * @since 2.9.0
813
+ *
814
+ * @param string $excerpt The excerpt to use.
815
+ * @param bool $page_id The current page/term ID
816
+ * @param object|mixed $term The current term.
817
+ * @param int $max_char_length Determines the maximum length of excerpt after trimming.
818
+ */
819
+ $excerpt = (string) \apply_filters( 'the_seo_framework_fetched_description_excerpt', $excerpt, $page_id, $term, $max_char_length );
820
+
821
  $excerpt_cache[ $page_id ][ $term_id ] = $excerpt;
822
  }
823
 
inc/classes/generate-image.class.php CHANGED
@@ -49,33 +49,18 @@ class Generate_Image extends Generate_Url {
49
  }
50
 
51
  /**
52
- * Fetches og:image URL.
53
  *
54
- * @since 2.2.2
55
- * @since 2.2.8 : Added theme icon detection.
56
- * @since 2.5.2 : Added args filters.
57
- * @since 2.8.0 : 1. Added theme logo detection.
58
- * 2. Added inpost image selection detection.
59
- * @since 2.8.2 : 1. Now returns something on post ID 0.
60
- * 2. Added SEO settings fallback image selection detection.
61
  *
62
  * @todo listen to attached images within post.
63
- * @todo set archive and front page image listener, now it simply fail on some calls.
64
- * @priority medium 2.7.0+
65
  *
66
- * @param string $post_id The post ID.
67
  * @param array $args The image arguments.
68
- * @param bool $escape Whether to escape the image URL.
69
- * @return string the image URL.
70
  */
71
- public function get_image( $post_id = '', $args = array(), $escape = true ) {
72
-
73
- /**
74
- * Backwards compat with parse args.
75
- * @since 2.5.0
76
- */
77
- if ( ! isset( $args['post_id'] ) )
78
- $args['post_id'] = $post_id ?: ( $this->is_singular() ? $this->get_the_real_ID() : 0 );
79
 
80
  $args = $this->reparse_image_args( $args );
81
 
@@ -88,27 +73,36 @@ class Generate_Image extends Generate_Url {
88
  //* Check if there are no disallowed arguments.
89
  $all_allowed = empty( $args['disallowed'] );
90
 
 
 
 
 
 
 
91
  if ( $args['post_id'] ) {
92
- //* 1. Fetch image from SEO meta upload.
93
  if ( $all_allowed || false === in_array( 'postmeta', $args['disallowed'], true ) ) {
94
  if ( $image = $this->get_social_image_url_from_post_meta( $args['post_id'], true ) )
95
  goto end;
96
  }
97
 
98
- //* 2. Fetch image from featured
99
  if ( $all_allowed || false === in_array( 'featured', $args['disallowed'], true ) ) {
100
- if ( $image = $this->get_image_from_post_thumbnail( $args ) )
101
  goto end;
102
  }
103
  }
104
 
105
- //* 3. Fetch image from SEO settings
 
 
 
106
  if ( $all_allowed || false === in_array( 'option', $args['disallowed'], true ) ) {
107
  if ( $image = $this->get_social_image_url_from_seo_settings( true ) )
108
  goto end;
109
  }
110
 
111
- //* 4. Fetch image from fallback filter 1
112
  /**
113
  * Applies filters 'the_seo_framework_og_image_after_featured' : string
114
  * @since 2.5.2
@@ -118,13 +112,13 @@ class Generate_Image extends Generate_Url {
118
  goto end;
119
  }
120
 
121
- //* 5. Fallback: Get header image if exists
122
  if ( ( $all_allowed || false === in_array( 'header', $args['disallowed'], true ) ) && \current_theme_supports( 'custom-header', 'default-image' ) ) {
123
  if ( $image = $this->get_header_image( true ) )
124
  goto end;
125
  }
126
 
127
- //* 6. Fetch image from fallback filter 2
128
  /**
129
  * Applies filters 'the_seo_framework_og_image_after_header' : string
130
  * @since 2.5.2
@@ -134,13 +128,13 @@ class Generate_Image extends Generate_Url {
134
  goto end;
135
  }
136
 
137
- //* 7. Get the WP 4.5 Site Logo
138
  if ( ( $all_allowed || false === in_array( 'logo', $args['disallowed'], true ) ) && $this->can_use_logo() ) {
139
  if ( $image = $this->get_site_logo( true ) )
140
  goto end;
141
  }
142
 
143
- //* 8. Get the WP 4.3 Site Icon
144
  if ( $all_allowed || false === in_array( 'icon', $args['disallowed'], true ) ) {
145
  if ( $image = $this->get_site_icon( 'full', true ) )
146
  goto end;
@@ -148,16 +142,19 @@ class Generate_Image extends Generate_Url {
148
 
149
  end :;
150
 
151
- if ( $escape && $image )
152
- return \esc_url( $image );
153
 
154
- return $image;
155
  }
156
 
157
  /**
158
  * Parse and sanitize image args.
159
  *
160
  * @since 2.5.0
 
 
 
161
  *
162
  * The image set in the filter will always be used as fallback
163
  *
@@ -175,8 +172,9 @@ class Generate_Image extends Generate_Url {
175
  'image' => '',
176
  'size' => 'full',
177
  'icon' => false,
178
- 'attr' => array(),
179
  'disallowed' => array(),
 
180
  );
181
 
182
  /**
@@ -184,7 +182,7 @@ class Generate_Image extends Generate_Url {
184
  * @param string $image The image url
185
  * @param mixed $size The image size
186
  * @param bool $icon Fetch Image icon
187
- * @param array $attr Image attributes
188
  * @param array $disallowed Disallowed image types : {
189
  * array (
190
  * string 'featured'
@@ -192,6 +190,7 @@ class Generate_Image extends Generate_Url {
192
  * string 'icon'
193
  * )
194
  * }
 
195
  * }
196
  *
197
  * @since 2.0.1
@@ -207,1