Advanced Ads - Version 1.31.0

Version Description

  • Feature: add "Expired" and "Expiring" views to the ad overview list replacing the expiry date filter
  • Improvement: use "saved" dashicon when an element was saved correctly or a process finished
  • Improvement: don't report HTML5 tags or custom elements as invalid tags in custom ad content
  • Improvement: optimize warnings from AdSense account and clarify that these warnings are not from Advanced Ads
  • Improvement: separate inject_in_content code into class Advanced_Ads_In_Content_Injector
  • Improvement: update video manual display conditions
Download this release

Release Info

Developer advancedads
Plugin Icon 128x128 Advanced Ads
Version 1.31.0
Comparing to
See all releases

Code changes from version 1.30.5 to 1.31.0

admin/assets/css/admin.css CHANGED
@@ -399,6 +399,16 @@ body.rtl #mapi-archived-ads { left: 78px; right:auto; }
399
  cursor: pointer;
400
  }
401
 
 
 
 
 
 
 
 
 
 
 
402
  /**
403
  * Connect to Google AdSense modal
404
  */
@@ -664,6 +674,11 @@ li:hover > .advads-help:before {
664
  font-size: 20px;
665
  vertical-align: top;
666
  }
 
 
 
 
 
667
 
668
  /* Inline Notifications */
669
  span.advads-notice-inline {
@@ -685,6 +700,12 @@ span.advads-notice-inline {
685
  .advads-idea:before {
686
  content: "\f339";
687
  }
 
 
 
 
 
 
688
  .advads-manual:before {
689
  content: "\f118";
690
  }
399
  cursor: pointer;
400
  }
401
 
402
+ @media screen and (min-width: 1368px) {
403
+ #mapi-account-alerts {
404
+ float: right;
405
+ }
406
+
407
+ #mapi-account-alerts .card {
408
+ margin-top: 0;
409
+ }
410
+ }
411
+
412
  /**
413
  * Connect to Google AdSense modal
414
  */
674
  font-size: 20px;
675
  vertical-align: top;
676
  }
677
+ .advads-notice-block:after {
678
+ content: "";
679
+ clear: both;
680
+ display: block;
681
+ }
682
 
683
  /* Inline Notifications */
684
  span.advads-notice-inline {
700
  .advads-idea:before {
701
  content: "\f339";
702
  }
703
+ .advads-check:before {
704
+ content: "\f15e";
705
+ color: #1e610f;
706
+ font-size: 24px;
707
+ line-height: 20px;
708
+ }
709
  .advads-manual:before {
710
  content: "\f118";
711
  }
admin/assets/js/admin-global.js CHANGED
@@ -148,9 +148,7 @@ jQuery( document ).ready(function () {
148
  e.preventDefault();
149
  var self = jQuery( this );
150
  // set cookie for 30 days
151
- var exdate = new Date();
152
- exdate.setSeconds( exdate.getSeconds() + 2592000 );
153
- document.cookie = "advanced_ads_hide_deactivate_feedback=1; expires=" + exdate.toUTCString() + "; path=/";
154
  // save if plugin should be disabled
155
  var disable_plugin = self.hasClass('advanced-ads-feedback-not-deactivate') ? false : true;
156
 
@@ -194,11 +192,13 @@ jQuery( document ).ready(function () {
194
  // show feedback message
195
  jQuery( '#advanced-ads-feedback-after-submit-goodbye' ).show();
196
  jQuery( '#advanced-ads-feedback-after-submit-disabling-plugin' ).show();
197
- // wait 3 seconds
 
 
198
  setTimeout(function(){
199
  jQuery( '#advanced-ads-feedback-overlay' ).hide();
200
  window.location.href = advads_deactivate_link_url;
201
- }, 3000);
202
  });
203
  // close button for feedback form
204
  jQuery('#advanced-ads-feedback-overlay-close-button').on('click', function () {
@@ -225,6 +225,16 @@ function advads_admin_get_cookie (name) {
225
  }
226
  }
227
 
 
 
 
 
 
 
 
 
 
 
228
  /**
229
  * load RSS widget on dashboard page using AJAX to not block rendering the rest of the page
230
  */
148
  e.preventDefault();
149
  var self = jQuery( this );
150
  // set cookie for 30 days
151
+ advads_store_feedback_cookie();
 
 
152
  // save if plugin should be disabled
153
  var disable_plugin = self.hasClass('advanced-ads-feedback-not-deactivate') ? false : true;
154
 
192
  // show feedback message
193
  jQuery( '#advanced-ads-feedback-after-submit-goodbye' ).show();
194
  jQuery( '#advanced-ads-feedback-after-submit-disabling-plugin' ).show();
195
+ // set cookie for 30 days
196
+ advads_store_feedback_cookie();
197
+ // wait one second
198
  setTimeout(function(){
199
  jQuery( '#advanced-ads-feedback-overlay' ).hide();
200
  window.location.href = advads_deactivate_link_url;
201
+ }, 1000 );
202
  });
203
  // close button for feedback form
204
  jQuery('#advanced-ads-feedback-overlay-close-button').on('click', function () {
225
  }
226
  }
227
 
228
+ /**
229
+ * Store a cookie for 30 days
230
+ * The cookie prevents the feedback form from showing multiple times
231
+ */
232
+ function advads_store_feedback_cookie() {
233
+ var exdate = new Date();
234
+ exdate.setSeconds( exdate.getSeconds() + 2592000 );
235
+ document.cookie = "advanced_ads_hide_deactivate_feedback=1; expires=" + exdate.toUTCString() + "; path=/";
236
+ }
237
+
238
  /**
239
  * load RSS widget on dashboard page using AJAX to not block rendering the rest of the page
240
  */
admin/class-advanced-ads-admin.php CHANGED
@@ -456,7 +456,12 @@ class Advanced_Ads_Admin {
456
  if ( isset( $form['advanced_ads_disable_reason'] )
457
  && 'get help' === $form['advanced_ads_disable_reason']
458
  && ! empty( $form['advanced_ads_disable_reply_email'] ) ) {
459
- $email = isset( $form['advanced_ads_disable_reply_email'] ) ? trim( $form['advanced_ads_disable_reply_email'] ) : $current_user->email;
 
 
 
 
 
460
  $current_user = wp_get_current_user();
461
  $name = ( $current_user instanceof WP_User ) ? $current_user->user_nicename : '';
462
  $from = $name . ' <' . $email . '>';
456
  if ( isset( $form['advanced_ads_disable_reason'] )
457
  && 'get help' === $form['advanced_ads_disable_reason']
458
  && ! empty( $form['advanced_ads_disable_reply_email'] ) ) {
459
+
460
+ $email = trim( $form['advanced_ads_disable_reply_email'] );
461
+ if ( ! is_email( $email ) ) {
462
+ die();
463
+ }
464
+
465
  $current_user = wp_get_current_user();
466
  $name = ( $current_user instanceof WP_User ) ? $current_user->user_nicename : '';
467
  $from = $name . ' <' . $email . '>';
admin/includes/class-ad-type.php CHANGED
@@ -86,14 +86,9 @@ class Advanced_Ads_Admin_Ad_Type {
86
  2
87
  );
88
 
89
- // handling (ad) lists.
90
- add_filter(
91
- 'request',
92
- array(
93
- $this,
94
- 'ad_list_request',
95
- )
96
- ); // order ads by title, not ID.
97
  add_action(
98
  'all_admin_notices',
99
  array(
@@ -239,11 +234,12 @@ class Advanced_Ads_Admin_Ad_Type {
239
  $ad_dom->loadHtml( '<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=' . $wp_charset . '" /><body>' . $ad_content );
240
 
241
  $errors = '';
242
- foreach ( libxml_get_errors() as $_error ) {
243
- // continue, if there is '&' symbol, but not HTML entity.
244
- if ( false === stripos( $_error->message, 'htmlParseEntityRef:' ) ) {
245
- $errors .= print_r( $_error, true );
246
  }
 
247
  }
248
 
249
  libxml_use_internal_errors( $libxml_previous_state );
@@ -457,25 +453,53 @@ class Advanced_Ads_Admin_Ad_Type {
457
  *
458
  * @param array $vars array with request vars.
459
  *
460
- * @return array $vars
461
- * @since 1.3.18
462
  */
463
  public function ad_list_request( $vars ) {
 
 
 
 
 
 
 
 
 
464
 
465
- // order ads by title on ads list.
466
- if ( is_admin() && empty( $vars['orderby'] ) && isset( $vars['post_type'] ) && $this->post_type === $vars['post_type'] ) {
467
- $vars = array_merge(
468
- $vars,
469
- array(
470
- 'orderby' => 'title',
471
- 'order' => 'ASC',
472
- )
473
- );
474
- // set get parameters, so the column header arrow shows up correctly.
475
- $_GET['orderby'] = $vars['orderby'];
476
- $_GET['order'] = $vars['order'];
477
  }
478
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
479
  return $vars;
480
  }
481
 
86
  2
87
  );
88
 
89
+ // order ad lists.
90
+ add_filter( 'request', array( $this, 'ad_list_request' ) );
91
+
 
 
 
 
 
92
  add_action(
93
  'all_admin_notices',
94
  array(
234
  $ad_dom->loadHtml( '<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=' . $wp_charset . '" /><body>' . $ad_content );
235
 
236
  $errors = '';
237
+ foreach ( libxml_get_errors() as $error ) {
238
+ // continue, if there is '&' symbol, but not HTML entity; or if an "invalid" tag is found.
239
+ if ( stripos( $error->message, 'htmlParseEntityRef:' ) || preg_match( '/tag \S+ invalid/i', $error->message ) ) {
240
+ continue;
241
  }
242
+ $errors .= print_r( $error, true );
243
  }
244
 
245
  libxml_use_internal_errors( $libxml_previous_state );
453
  *
454
  * @param array $vars array with request vars.
455
  *
456
+ * @return array
 
457
  */
458
  public function ad_list_request( $vars ) {
459
+ // if we shouldn't filter this return $vars array.
460
+ if (
461
+ ! isset( $vars['post_type'] )
462
+ || $vars['post_type'] !== Advanced_Ads::POST_TYPE_SLUG
463
+ || ! is_admin()
464
+ || wp_doing_ajax()
465
+ ) {
466
+ return $vars;
467
+ }
468
 
469
+ // order ads by title on ads list by default
470
+ if ( empty( $vars['orderby'] ) ) {
471
+ return $this->default_ad_list_order( $vars );
 
 
 
 
 
 
 
 
 
472
  }
473
 
474
+ if ( $vars['orderby'] === 'expiry_date' ) {
475
+ $vars['orderby'] = 'meta_value';
476
+ $vars['meta_key'] = Advanced_Ads_Ad_Expiration::POST_META;
477
+ $vars['order'] = strtoupper( $vars['order'] ) === 'DESC' ? 'DESC' : 'ASC';
478
+
479
+ if ( isset( $_GET['addate'] ) && $_GET['addate'] === 'advads-filter-expired' ) {
480
+ $vars['post_status'] = Advanced_Ads_Ad_Expiration::POST_STATUS;
481
+ }
482
+ }
483
+
484
+ return $vars;
485
+ }
486
+
487
+ /**
488
+ * Set query vars and $_GET parameters for default ad list order.
489
+ *
490
+ * @param array $vars request args.
491
+ *
492
+ * @return array
493
+ */
494
+ private function default_ad_list_order( $vars ) {
495
+ $vars = array_merge( $vars, array(
496
+ 'orderby' => 'title',
497
+ 'order' => 'ASC',
498
+ ) );
499
+ // set get parameters, so the column header arrow shows up correctly.
500
+ $_GET['orderby'] = $vars['orderby'];
501
+ $_GET['order'] = $vars['order'];
502
+
503
  return $vars;
504
  }
505
 
admin/includes/class-licenses.php CHANGED
@@ -167,7 +167,8 @@ class Advanced_Ads_Admin_Licenses {
167
 
168
  // show license debug output if constant is set.
169
  if ( defined( 'ADVANCED_ADS_SHOW_LICENSE_RESPONSE' ) ) {
170
- return '<pre>' . print_r( $response, true ) . '</pre>';
 
171
  }
172
 
173
  /**
@@ -356,7 +357,8 @@ class Advanced_Ads_Admin_Licenses {
356
 
357
  // show license debug output if constant is set.
358
  if ( defined( 'ADVANCED_ADS_SHOW_LICENSE_RESPONSE' ) ) {
359
- return '<pre>' . print_r( $response, true ) . '</pre>';
 
360
  }
361
 
362
  if ( is_wp_error( $response ) ) {
167
 
168
  // show license debug output if constant is set.
169
  if ( defined( 'ADVANCED_ADS_SHOW_LICENSE_RESPONSE' ) ) {
170
+ return '<p><strong>' . esc_html__( 'The license status does not change as long as ADVANCED_ADS_SHOW_LICENSE_RESPONSE is enabled in wp-config.php.', 'advanced-ads' ) . '</strong></p>' .
171
+ '<pre>' . print_r( $response, true ) . '</pre>';
172
  }
173
 
174
  /**
357
 
358
  // show license debug output if constant is set.
359
  if ( defined( 'ADVANCED_ADS_SHOW_LICENSE_RESPONSE' ) ) {
360
+ return '<p><strong>' . esc_html__( 'The license status does not change as long as ADVANCED_ADS_SHOW_LICENSE_RESPONSE is enabled in wp-config.php.', 'advanced-ads' ) . '</strong></p>' .
361
+ '<pre>' . print_r( $response, true ) . '</pre>';
362
  }
363
 
364
  if ( is_wp_error( $response ) ) {
admin/includes/class-list-filters.php CHANGED
@@ -38,6 +38,7 @@ class Advanced_Ads_Ad_List_Filters {
38
  * Ads array with ID as key
39
  *
40
  * @var array
 
41
  */
42
  protected $adsbyid = array();
43
 
@@ -63,6 +64,9 @@ class Advanced_Ads_Ad_List_Filters {
63
  add_filter( 'posts_results', array( $this, 'post_results' ), 10, 2 );
64
  add_filter( 'post_limits', array( $this, 'limit_filter' ), 10, 2 );
65
  }
 
 
 
66
  }
67
 
68
  /**
@@ -82,7 +86,6 @@ class Advanced_Ads_Ad_List_Filters {
82
  $all_filters = array(
83
  'all_sizes' => array(),
84
  'all_types' => array(),
85
- 'all_dates' => array(),
86
  'all_groups' => array(),
87
  );
88
 
@@ -99,10 +102,6 @@ class Advanced_Ads_Ad_List_Filters {
99
  $groups_to_check = $this->ads_in_groups;
100
 
101
  foreach ( $posts as $post ) {
102
-
103
- if ( ! isset( $this->all_ads_options[ $post->ID ] ) ) {
104
- continue;
105
- }
106
  $ad_option = $this->all_ads_options[ $post->ID ];
107
 
108
  /**
@@ -128,8 +127,7 @@ class Advanced_Ads_Ad_List_Filters {
128
  }
129
 
130
  if ( isset( $ad_option['type'] ) && 'adsense' === $ad_option['type'] ) {
131
- $content = $this->adsbyid[ $post->ID ]->post_content;
132
- $adsense_obj = false;
133
  try {
134
  $adsense_obj = json_decode( $content, true );
135
  } catch ( Exception $e ) {
@@ -145,18 +143,6 @@ class Advanced_Ads_Ad_List_Filters {
145
  }
146
  }
147
 
148
- if ( isset( $ad_option['expiry_date'] ) && $ad_option['expiry_date'] ) {
149
- if ( time() >= absint( $ad_option['expiry_date'] ) ) {
150
- if ( ! array_key_exists( 'advads-filter-expired', $all_filters['all_dates'] ) ) {
151
- $all_filters['all_dates']['advads-filter-expired'] = __( 'expired', 'advanced-ads' );
152
- }
153
- } else {
154
- if ( ! array_key_exists( 'advads-filter-any-exp-date', $all_filters['all_dates'] ) ) {
155
- $all_filters['all_dates']['advads-filter-any-exp-date'] = __( 'any expiry date', 'advanced-ads' );
156
- }
157
- }
158
- }
159
-
160
  if ( isset( $ad_option['type'] ) // could be missing for new ads that are stored only by WP auto-save.
161
  && ! array_key_exists( $ad_option['type'], $all_filters['all_types'] )
162
  && isset( $advads->ad_types[ $ad_option['type'] ] )
@@ -174,27 +160,34 @@ class Advanced_Ads_Ad_List_Filters {
174
  /**
175
  * Collects all ads data.
176
  *
177
- * @param array $posts array of ads.
178
  */
179
  public function collect_all_ads( $posts ) {
180
- $postsbyid = array();
181
-
182
  foreach ( $posts as $post ) {
183
- $postsbyid[ $post->ID ] = $post;
 
 
 
 
 
 
 
 
 
 
184
  }
185
 
186
- global $wpdb;
187
- $meta_results = $wpdb->get_results( $wpdb->prepare( 'SELECT post_id, meta_value FROM `' . $wpdb->prefix . 'postmeta` WHERE `meta_key` = %s', 'advanced_ads_ad_options' ), 'ARRAY_A' );
188
 
189
- $options = array();
190
- foreach ( $meta_results as $_value ) {
191
- $value = maybe_unserialize( $_value['meta_value'] );
192
- $options[ $_value['post_id'] ] = $value;
193
- }
194
 
195
  $_groups = Advanced_Ads::get_ad_groups();
196
-
197
- $groups = array();
198
 
199
  /**
200
  * It looks like there might be a third-party conflict we haven’t been able to reproduce that causes the group
@@ -235,13 +228,6 @@ class Advanced_Ads_Ad_List_Filters {
235
  }
236
  }
237
 
238
- /**
239
- * Store all data
240
- */
241
- $this->all_ads = $posts;
242
- $this->adsbyid = $postsbyid;
243
- $this->all_ads_options = $options;
244
-
245
  $this->all_groups = $groups;
246
  }
247
 
@@ -295,11 +281,7 @@ class Advanced_Ads_Ad_List_Filters {
295
  */
296
  public function post_results( $posts, $the_query ) {
297
  // Execute only in the main query.
298
- if ( ! $the_query->is_main_query() ) {
299
- return $posts;
300
- }
301
-
302
- if ( ! function_exists( 'get_current_screen' ) ) {
303
  return $posts;
304
  }
305
 
@@ -310,6 +292,7 @@ class Advanced_Ads_Ad_List_Filters {
310
  }
311
 
312
  $this->collect_all_ads( $posts );
 
313
 
314
  // the new post list.
315
  if ( isset( $_REQUEST['post_status'] ) && 'trash' === $_REQUEST['post_status'] ) {
@@ -438,11 +421,9 @@ class Advanced_Ads_Ad_List_Filters {
438
  $new_posts = array();
439
  $the_list = $using_original ? $this->all_ads : $posts;
440
  foreach ( $the_list as $post ) {
441
- if ( isset( $this->all_ads_options[ $post->ID ] ) ) {
442
- $option = $this->all_ads_options[ $post->ID ];
443
- if ( isset( $option['type'] ) && $request['adtype'] === $option['type'] ) {
444
- $new_posts[] = $post;
445
- }
446
  }
447
  }
448
  $posts = $new_posts;
@@ -457,27 +438,25 @@ class Advanced_Ads_Ad_List_Filters {
457
  $new_posts = array();
458
  $the_list = $using_original ? $this->all_ads : $posts;
459
  foreach ( $the_list as $post ) {
460
- if ( isset( $this->all_ads_options[ $post->ID ] ) ) {
461
- $option = $this->all_ads_options[ $post->ID ];
462
- if ( 'responsive' === $request['adsize'] ) {
463
- if ( 'adsense' === $option['type'] ) {
 
 
 
464
  $content = false;
465
- try {
466
- $content = json_decode( $post->post_content, true );
467
- } catch ( Exception $e ) {
468
- $content = false;
469
- }
470
- if ( $content && 'responsive' === $content['unitType'] ) {
471
- $new_posts[] = $post;
472
- }
473
  }
474
- } else {
475
- $width = isset( $option['width'] ) ? $option['width'] : 0;
476
- $height = isset( $option['height'] ) ? $option['height'] : 0;
477
- if ( $request['adsize'] === $width . 'x' . $height ) {
478
  $new_posts[] = $post;
479
  }
480
  }
 
 
 
 
 
 
481
  }
482
  }
483
  $posts = $new_posts;
@@ -485,39 +464,8 @@ class Advanced_Ads_Ad_List_Filters {
485
  $using_original = false;
486
  }
487
 
488
- /**
489
- * Filter ad timing
490
- */
491
- if ( isset( $request['addate'] ) && '' !== $request['addate'] ) {
492
- if ( 'advads-filter-any-exp-date' === urldecode( $request['addate'] ) ) {
493
- $new_posts = array();
494
- $the_list = $using_original ? $this->all_ads : $posts;
495
- foreach ( $the_list as $post ) {
496
- if ( isset( $this->all_ads_options[ $post->ID ] ) ) {
497
- $option = $this->all_ads_options[ $post->ID ];
498
- if ( ! empty( $option['expiry_date'] ) ) {
499
- $new_posts[] = $post;
500
- }
501
- }
502
- }
503
- $posts = $new_posts;
504
- $the_query->found_posts = count( $posts );
505
- $using_original = false;
506
- } elseif ( 'advads-filter-expired' === urldecode( $request['addate'] ) ) {
507
- $new_posts = array();
508
- $the_list = $using_original ? $this->all_ads : $posts;
509
- foreach ( $the_list as $post ) {
510
- if ( isset( $this->all_ads_options[ $post->ID ] ) ) {
511
- $option = $this->all_ads_options[ $post->ID ];
512
- if ( $option['expiry_date'] && time() >= $option['expiry_date'] ) {
513
- $new_posts[] = $post;
514
- }
515
- }
516
- }
517
- $posts = $new_posts;
518
- $the_query->found_posts = count( $posts );
519
- $using_original = false;
520
- }
521
  }
522
 
523
  $posts = apply_filters( 'advanced-ads-ad-list-filter', $posts, $this->all_ads_options );
@@ -539,4 +487,132 @@ class Advanced_Ads_Ad_List_Filters {
539
 
540
  return self::$instance;
541
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
542
  }
38
  * Ads array with ID as key
39
  *
40
  * @var array
41
+ * @deprecated 1.31.0 -- we don't needs ads indexed by id, since we have all ads.
42
  */
43
  protected $adsbyid = array();
44
 
64
  add_filter( 'posts_results', array( $this, 'post_results' ), 10, 2 );
65
  add_filter( 'post_limits', array( $this, 'limit_filter' ), 10, 2 );
66
  }
67
+
68
+ add_filter( 'views_edit-' . Advanced_Ads::POST_TYPE_SLUG, array( $this, 'add_expired_view' ) );
69
+ add_filter( 'views_edit-' . Advanced_Ads::POST_TYPE_SLUG, array( $this, 'add_expiring_view' ) );
70
  }
71
 
72
  /**
86
  $all_filters = array(
87
  'all_sizes' => array(),
88
  'all_types' => array(),
 
89
  'all_groups' => array(),
90
  );
91
 
102
  $groups_to_check = $this->ads_in_groups;
103
 
104
  foreach ( $posts as $post ) {
 
 
 
 
105
  $ad_option = $this->all_ads_options[ $post->ID ];
106
 
107
  /**
127
  }
128
 
129
  if ( isset( $ad_option['type'] ) && 'adsense' === $ad_option['type'] ) {
130
+ $content = $this->all_ads[ array_search( $post->ID, wp_list_pluck( $this->all_ads, 'ID' ), true ) ]->post_content;
 
131
  try {
132
  $adsense_obj = json_decode( $content, true );
133
  } catch ( Exception $e ) {
143
  }
144
  }
145
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  if ( isset( $ad_option['type'] ) // could be missing for new ads that are stored only by WP auto-save.
147
  && ! array_key_exists( $ad_option['type'], $all_filters['all_types'] )
148
  && isset( $advads->ad_types[ $ad_option['type'] ] )
160
  /**
161
  * Collects all ads data.
162
  *
163
+ * @param WP_Post[] $posts array of ads.
164
  */
165
  public function collect_all_ads( $posts ) {
 
 
166
  foreach ( $posts as $post ) {
167
+ $this->adsbyid[ $post->ID ] = $post;
168
+ $this->all_ads_options[ $post->ID ] = get_post_meta( $post->ID, 'advanced_ads_ad_options', true );
169
+ if ( empty( $this->all_ads_options[ $post->ID ] ) ) {
170
+ $this->all_ads_options[ $post->ID ] = array();
171
+ }
172
+
173
+ // convert all expiration dates.
174
+ $ad = new Advanced_Ads_Ad( $post->ID );
175
+ $expiration = new Advanced_Ads_Ad_Expiration( $ad );
176
+ $expiration->save_expiration_date( $this->all_ads_options[ $post->ID ], $ad );
177
+ $expiration->is_ad_expired();
178
  }
179
 
180
+ $this->all_ads = $posts;
181
+ }
182
 
183
+ /**
184
+ * Collects all ads groups, fills the $all_groups class property.
185
+ */
186
+ private function collect_all_groups() {
187
+ global $wpdb;
188
 
189
  $_groups = Advanced_Ads::get_ad_groups();
190
+ $groups = array();
 
191
 
192
  /**
193
  * It looks like there might be a third-party conflict we haven’t been able to reproduce that causes the group
228
  }
229
  }
230
 
 
 
 
 
 
 
 
231
  $this->all_groups = $groups;
232
  }
233
 
281
  */
282
  public function post_results( $posts, $the_query ) {
283
  // Execute only in the main query.
284
+ if ( ! function_exists( 'get_current_screen' ) || ! $the_query->is_main_query() ) {
 
 
 
 
285
  return $posts;
286
  }
287
 
292
  }
293
 
294
  $this->collect_all_ads( $posts );
295
+ $this->collect_all_groups();
296
 
297
  // the new post list.
298
  if ( isset( $_REQUEST['post_status'] ) && 'trash' === $_REQUEST['post_status'] ) {
421
  $new_posts = array();
422
  $the_list = $using_original ? $this->all_ads : $posts;
423
  foreach ( $the_list as $post ) {
424
+ $option = $this->all_ads_options[ $post->ID ];
425
+ if ( isset( $option['type'] ) && $request['adtype'] === $option['type'] ) {
426
+ $new_posts[] = $post;
 
 
427
  }
428
  }
429
  $posts = $new_posts;
438
  $new_posts = array();
439
  $the_list = $using_original ? $this->all_ads : $posts;
440
  foreach ( $the_list as $post ) {
441
+ $option = $this->all_ads_options[ $post->ID ];
442
+ if ( 'responsive' === $request['adsize'] ) {
443
+ if ( 'adsense' === $option['type'] ) {
444
+ $content = false;
445
+ try {
446
+ $content = json_decode( $post->post_content, true );
447
+ } catch ( Exception $e ) {
448
  $content = false;
 
 
 
 
 
 
 
 
449
  }
450
+ if ( $content && 'responsive' === $content['unitType'] ) {
 
 
 
451
  $new_posts[] = $post;
452
  }
453
  }
454
+ } else {
455
+ $width = isset( $option['width'] ) ? $option['width'] : 0;
456
+ $height = isset( $option['height'] ) ? $option['height'] : 0;
457
+ if ( $request['adsize'] === $width . 'x' . $height ) {
458
+ $new_posts[] = $post;
459
+ }
460
  }
461
  }
462
  $posts = $new_posts;
464
  $using_original = false;
465
  }
466
 
467
+ if ( isset( $request['addate'] ) ) {
468
+ $posts = $this->filter_expired_ads( urldecode( $request['addate'] ), $using_original ? $this->all_ads : $posts );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
469
  }
470
 
471
  $posts = apply_filters( 'advanced-ads-ad-list-filter', $posts, $this->all_ads_options );
487
 
488
  return self::$instance;
489
  }
490
+
491
+ /**
492
+ * If there are expired ads, add an expired view.
493
+ *
494
+ * @param array $views currently available views.
495
+ *
496
+ * @return array
497
+ */
498
+ public function add_expired_view( $views ) {
499
+ $count = $this->count_expired_ads();
500
+ if ( empty( $count ) ) {
501
+ return $views;
502
+ }
503
+ $views[ Advanced_Ads_Ad_Expiration::POST_STATUS ] = sprintf(
504
+ '<a href="%s" %s>%s <span class="count">(%d)</span></a>',
505
+ add_query_arg( array(
506
+ 'post_type' => Advanced_Ads::POST_TYPE_SLUG,
507
+ 'addate' => 'advads-filter-expired',
508
+ 'orderby' => 'expiry_date',
509
+ 'order' => 'DESC',
510
+ ), 'edit.php' ),
511
+ isset( $_REQUEST['addate'] ) && $_REQUEST['addate'] === 'advads-filter-expired' ? 'class="current" aria-current="page"' : '',
512
+ esc_attr_x( 'Expired', 'Post list header for expired ads.', 'advanced-ads' ),
513
+ $count
514
+ );
515
+
516
+ return array_replace( array_intersect_key( $this->views_order(), $views ), $views );
517
+ }
518
+
519
+ /**
520
+ * Get the number of ads that have expired.
521
+ *
522
+ * @return int
523
+ */
524
+ private function count_expired_ads() {
525
+ return ( new WP_Query( array(
526
+ 'post_type' => Advanced_Ads::POST_TYPE_SLUG,
527
+ 'post_status' => Advanced_Ads_Ad_Expiration::POST_STATUS,
528
+ ) ) )->found_posts;
529
+ }
530
+
531
+ /**
532
+ * If there are ads with an expiration date in the future, add an expiring view.
533
+ *
534
+ * @param array $views currently available views.
535
+ *
536
+ * @return array
537
+ */
538
+ public function add_expiring_view( $views ) {
539
+ $count = $this->count_expiring_ads();
540
+ if ( empty( $count ) ) {
541
+ return $views;
542
+ }
543
+ $views['expiring'] = sprintf(
544
+ '<a href="%s" %s>%s <span class="count">(%d)</span></a>',
545
+ add_query_arg( array(
546
+ 'post_type' => Advanced_Ads::POST_TYPE_SLUG,
547
+ 'addate' => 'advads-filter-expiring',
548
+ 'orderby' => 'expiry_date',
549
+ 'order' => 'ASC',
550
+ ), 'edit.php' ),
551
+ isset( $_REQUEST['addate'] ) && $_REQUEST['addate'] === 'advads-filter-expiring' ? 'class="current" aria-current="page"' : '',
552
+ esc_attr_x( 'Expiring', 'Post list header for ads expiring in the future.', 'advanced-ads' ),
553
+ $count
554
+ );
555
+
556
+ return array_replace( array_intersect_key( $this->views_order(), $views ), $views );
557
+ }
558
+
559
+ /**
560
+ * Get the number of ads that have an expiration date in the future.
561
+ *
562
+ * @return int
563
+ */
564
+ private function count_expiring_ads() {
565
+ return ( new WP_Query( array(
566
+ 'post_type' => Advanced_Ads::POST_TYPE_SLUG,
567
+ 'post_status' => 'any',
568
+ 'meta_query' => array(
569
+ array(
570
+ 'key' => Advanced_Ads_Ad_Expiration::POST_META,
571
+ 'value' => current_time( 'mysql', true ),
572
+ 'compare' => '>=',
573
+ 'type' => 'DATETIME',
574
+ ),
575
+ ),
576
+ ) ) )->found_posts;
577
+ }
578
+
579
+ /**
580
+ * Our expected order of views.
581
+ *
582
+ * @return string[]
583
+ */
584
+ private function views_order() {
585
+ static $views_order;
586
+ if ( $views_order === null ) {
587
+ $views_order = array_flip( array( 'all', 'publish', 'future', 'expiring', Advanced_Ads_Ad_Expiration::POST_STATUS, 'draft', 'pending', 'trash' ) );
588
+ }
589
+
590
+ return $views_order;
591
+ }
592
+
593
+ /**
594
+ * Filter by expiring or expired ads.
595
+ *
596
+ * @param string $filter The current filter name, expired or expiring.
597
+ * @param WP_Post[] $posts The array of posts.
598
+ *
599
+ * @return WP_Post[]
600
+ */
601
+ private function filter_expired_ads( $filter, $posts ) {
602
+ $now = time();
603
+
604
+ return array_filter( $posts, function( WP_Post $post ) use ( $now, $filter ) {
605
+ $option = $this->all_ads_options[ $post->ID ];
606
+ if ( empty( $option['expiry_date'] ) ) {
607
+ return false;
608
+ }
609
+
610
+ return (
611
+ // filter by ads already expired.
612
+ ( $filter === 'advads-filter-expired' && $option['expiry_date'] <= $now )
613
+ // filter by ads expiring in the future.
614
+ || ( $filter === 'advads-filter-expiring' && $option['expiry_date'] > $now )
615
+ );
616
+ } );
617
+ }
618
  }
admin/includes/class-meta-box.php CHANGED
@@ -218,7 +218,7 @@ class Advanced_Ads_Admin_Meta_Boxes {
218
  $view = 'conditions/ad-display-metabox.php';
219
  $hndlelinks = '<a href="#" class="advads-video-link">' . __( 'Video', 'advanced-ads' ) . '</a>';
220
  $hndlelinks .= '<a href="' . ADVADS_URL . 'manual/display-conditions#utm_source=advanced-ads&utm_medium=link&utm_campaign=edit-display" target="_blank">' . __( 'Manual', 'advanced-ads' ) . '</a>';
221
- $videomarkup = '<iframe width="420" height="315" src="https://www.youtube-nocookie.com/embed/wVB6UpeyWNA?rel=0&amp;showinfo=0" frameborder="0" allowfullscreen></iframe>';
222
  break;
223
  case 'ad-visitor-box':
224
  $view = 'conditions/ad-visitor-metabox.php';
218
  $view = 'conditions/ad-display-metabox.php';
219
  $hndlelinks = '<a href="#" class="advads-video-link">' . __( 'Video', 'advanced-ads' ) . '</a>';
220
  $hndlelinks .= '<a href="' . ADVADS_URL . 'manual/display-conditions#utm_source=advanced-ads&utm_medium=link&utm_campaign=edit-display" target="_blank">' . __( 'Manual', 'advanced-ads' ) . '</a>';
221
+ $videomarkup = '<iframe width="420" height="315" src="https://www.youtube-nocookie.com/embed/VjfrRl5Qn4I?rel=0&amp;showinfo=0" frameborder="0" allowfullscreen></iframe>';
222
  break;
223
  case 'ad-visitor-box':
224
  $view = 'conditions/ad-visitor-metabox.php';
admin/views/ad-list-filters.php CHANGED
@@ -29,14 +29,6 @@ if ( isset( $_REQUEST['post_status'] ) && 'trash' === $_REQUEST['post_status'] )
29
  <?php endforeach; ?>
30
  </select>
31
  <?php endif; ?>
32
- <?php if ( ! empty( $all_filters['all_dates'] ) ) : ?>
33
- <select id="advads-filter-date" name="addate">
34
- <option value="">- <?php esc_html_e( 'all ad dates', 'advanced-ads' ); ?> -</option>
35
- <?php foreach ( $all_filters['all_dates'] as $key => $value ) : ?>
36
- <option <?php selected( $ad_date, $key ); ?> value="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $value ); ?></option>
37
- <?php endforeach; ?>
38
- </select>
39
- <?php endif; ?>
40
  <?php if ( ! empty( $all_filters['all_groups'] ) ) : ?>
41
  <select id="advads-filter-group" name="adgroup">
42
  <option value="">- <?php esc_html_e( 'all ad groups', 'advanced-ads' ); ?> -</option>
29
  <?php endforeach; ?>
30
  </select>
31
  <?php endif; ?>
 
 
 
 
 
 
 
 
32
  <?php if ( ! empty( $all_filters['all_groups'] ) ) : ?>
33
  <select id="advads-filter-group" name="adgroup">
34
  <option value="">- <?php esc_html_e( 'all ad groups', 'advanced-ads' ); ?> -</option>
admin/views/ad-submitbox-meta.php CHANGED
@@ -32,7 +32,6 @@
32
  ?>
33
  <fieldset class="advads-timestamp">
34
  <?php
35
- // phpcs:disable
36
  printf(
37
  // translators: %1$s month, %2$s day, %3$s year, %4$s hour, %5$s minute.
38
  _x( '%1$s %2$s, %3$s @ %4$s %5$s', 'order of expiry date fields 1: month, 2: day, 3: year, 4: hour, 5: minute', 'advanced-ads' ),
@@ -42,7 +41,6 @@
42
  $hour_field,
43
  $minute_field
44
  );
45
- // phpcs:enable
46
  ?>
47
  </fieldset>
48
  (<?php echo esc_html( Advanced_Ads_Utils::get_timezone_name() ); ?>)
32
  ?>
33
  <fieldset class="advads-timestamp">
34
  <?php
 
35
  printf(
36
  // translators: %1$s month, %2$s day, %3$s year, %4$s hour, %5$s minute.
37
  _x( '%1$s %2$s, %3$s @ %4$s %5$s', 'order of expiry date fields 1: month, 2: day, 3: year, 4: hour, 5: minute', 'advanced-ads' ),
41
  $hour_field,
42
  $minute_field
43
  );
 
44
  ?>
45
  </fieldset>
46
  (<?php echo esc_html( Advanced_Ads_Utils::get_timezone_name() ); ?>)
admin/views/placements.php CHANGED
@@ -227,8 +227,9 @@ $quick_actions['delete'] = '<a style="cursor: pointer;" class="advads-delete-tag
227
  <?php
228
  $modal_slug = esc_attr( $_placement_slug );
229
  $modal_content = $advanced_options;
230
- $modal_title = esc_html__( 'Options', 'advanced-ads' );
231
- $close_action = esc_html__( 'Close and save', 'advanced-ads' );
 
232
  include ADVADS_BASE_PATH . 'admin/views/modal.php';
233
  ?>
234
 
227
  <?php
228
  $modal_slug = esc_attr( $_placement_slug );
229
  $modal_content = $advanced_options;
230
+ // Translators: 1: "Options", 2: the name of a placement.
231
+ $modal_title = sprintf( '%1$s: %2$s', __( 'Options', 'advanced-ads' ), esc_html( $_placement['name'] ) );
232
+ $close_action = esc_html__( 'Close and save', 'advanced-ads' );
233
  include ADVADS_BASE_PATH . 'admin/views/modal.php';
234
  ?>
235
 
advanced-ads.php CHANGED
@@ -12,7 +12,7 @@
12
  * Plugin Name: Advanced Ads
13
  * Plugin URI: https://wpadvancedads.com
14
  * Description: Manage and optimize your ads in WordPress
15
- * Version: 1.30.5
16
  * Author: Thomas Maier, Advanced Ads GmbH
17
  * Author URI: https://wpadvancedads.com
18
  * Text Domain: advanced-ads
@@ -39,7 +39,7 @@ define( 'ADVADS_BASE_DIR', dirname( ADVADS_BASE ) ); // directory of the plugin
39
  // general and global slug, e.g. to store options in WP.
40
  define( 'ADVADS_SLUG', 'advanced-ads' );
41
  define( 'ADVADS_URL', 'https://wpadvancedads.com/' );
42
- define( 'ADVADS_VERSION', '1.30.5' );
43
 
44
  // Autoloading, modules and functions.
45
 
12
  * Plugin Name: Advanced Ads
13
  * Plugin URI: https://wpadvancedads.com
14
  * Description: Manage and optimize your ads in WordPress
15
+ * Version: 1.31.0
16
  * Author: Thomas Maier, Advanced Ads GmbH
17
  * Author URI: https://wpadvancedads.com
18
  * Text Domain: advanced-ads
39
  // general and global slug, e.g. to store options in WP.
40
  define( 'ADVADS_SLUG', 'advanced-ads' );
41
  define( 'ADVADS_URL', 'https://wpadvancedads.com/' );
42
+ define( 'ADVADS_VERSION', '1.31.0' );
43
 
44
  // Autoloading, modules and functions.
45
 
classes/ad-expiration.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Ad Expiration functionality.
5
+ */
6
+ class Advanced_Ads_Ad_Expiration {
7
+ const POST_STATUS = 'advanced_ads_expired';
8
+ const POST_META = 'advanced_ads_expiration_time';
9
+
10
+ /**
11
+ * The current ad object.
12
+ *
13
+ * @var Advanced_Ads_Ad
14
+ */
15
+ private $ad;
16
+
17
+ /**
18
+ * Inject ad object, hook to option saving.
19
+ *
20
+ * @param Advanced_Ads_Ad $ad the current ad object.
21
+ */
22
+ public function __construct( Advanced_Ads_Ad $ad ) {
23
+ $this->ad = $ad;
24
+
25
+ add_filter( 'advanced-ads-save-options', array( $this, 'save_expiration_date' ), 10, 2 );
26
+ }
27
+
28
+ /**
29
+ * Check whether this ad is expired.
30
+ *
31
+ * @return bool
32
+ */
33
+ public function is_ad_expired() {
34
+ if ( $this->ad->expiry_date <= 0 || $this->ad->expiry_date > time() ) {
35
+ return false;
36
+ }
37
+
38
+ // if the ad is not trashed, but has a different status than expired, transition the status.
39
+ if ( ! in_array( $this->ad->status, array( self::POST_STATUS, 'trash' ), true ) ) {
40
+ $this->transition_post_status();
41
+ }
42
+
43
+ return true;
44
+ }
45
+
46
+ /**
47
+ * Extract the expiration date from the options array and save it as post_meta directly.
48
+ *
49
+ * @param array $options array with all ad options.
50
+ * @param Advanced_Ads_Ad $ad the current ad object.
51
+ *
52
+ * @return array
53
+ */
54
+ public function save_expiration_date( $options, Advanced_Ads_Ad $ad ) {
55
+ if ( empty( $options['expiry_date'] ) ) {
56
+ return $options;
57
+ }
58
+ $datetime = ( new DateTimeImmutable() )->setTimestamp( (int) $options['expiry_date'] );
59
+ update_post_meta( $ad->id, self::POST_META, $datetime->format( 'Y-m-d H:i:s' ) );
60
+
61
+ return $options;
62
+ }
63
+
64
+ /**
65
+ * Transition the post form previous status to self::POST_STATUS.
66
+ * Remove kses filters before updating the post so that expiring ads don’t lose HTML or other code.
67
+ */
68
+ private function transition_post_status() {
69
+ kses_remove_filters();
70
+ wp_update_post(
71
+ array(
72
+ 'ID' => $this->ad->id,
73
+ 'post_status' => self::POST_STATUS,
74
+ )
75
+ );
76
+ kses_init_filters();
77
+ }
78
+
79
+ /**
80
+ * Register custom post status for expired ads.
81
+ */
82
+ public static function register_post_status() {
83
+ register_post_status( self::POST_STATUS, array(
84
+ 'label' => __( 'Expired', 'advanced-ads' ),
85
+ 'private' => true,
86
+ ) );
87
+ }
88
+
89
+ /**
90
+ * Hook into wp_untrash_post_status, to revert ads that previously had the expired status to that status instead of draft.
91
+ *
92
+ * @param string $new_status The new status after untrashing a post.
93
+ * @param int $post_id The post id of the post to be untrashed.
94
+ * @param string $previous_status The post status before trashing.
95
+ *
96
+ * @return string
97
+ */
98
+ public static function wp_untrash_post_status( $new_status, $post_id, $previous_status ) {
99
+ if ( $previous_status === self::POST_STATUS ) {
100
+ return $previous_status;
101
+ }
102
+
103
+ return $new_status;
104
+ }
105
+ }
classes/ad.php CHANGED
@@ -159,6 +159,19 @@ class Advanced_Ads_Ad {
159
  * @var Advanced_Ads_Inline_Css
160
  */
161
  private $inline_css;
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
  /**
164
  * Init ad object
@@ -238,7 +251,7 @@ class Advanced_Ads_Ad {
238
  $this->description = $this->options( 'description' );
239
  $this->output = $this->options( 'output' );
240
  $this->status = $_data->post_status;
241
- $this->expiry_date = $this->options( 'expiry_date' );
242
  $this->is_head_placement = isset( $this->args['placement_type'] ) && 'header' === $this->args['placement_type'];
243
  $this->args['is_top_level'] = ! isset( $this->args['is_top_level'] );
244
 
@@ -259,6 +272,8 @@ class Advanced_Ads_Ad {
259
  $this->wrapper['id'] = $this->create_wrapper_id();
260
  }
261
  }
 
 
262
  }
263
 
264
  /**
@@ -444,19 +459,19 @@ class Advanced_Ads_Ad {
444
  return false;
445
  }
446
 
447
- if ( ! $this->can_display_by_visitor() || ! $this->can_display_by_expiry_date() ) {
448
- return false;
449
- }
450
- } else {
451
- if ( 'publish' !== $this->status || ! $this->can_display_by_expiry_date() ) {
452
  return false;
453
  }
 
 
454
  }
455
 
456
- // add own conditions to flag output as possible or not.
457
- $can_display = apply_filters( 'advanced-ads-can-display', true, $this, $check_options );
 
458
 
459
- return $can_display;
 
460
  }
461
 
462
  /**
@@ -543,34 +558,10 @@ class Advanced_Ads_Ad {
543
  *
544
  * @return bool $can_display true if can be displayed in frontend based on expiry date
545
  * @since 1.3.15
 
546
  */
547
  public function can_display_by_expiry_date() {
548
-
549
- // if expiry_date is not set, null is returned.
550
- $ad_expiry_date = (int) $this->options( 'expiry_date' );
551
-
552
- if ( $ad_expiry_date <= 0 || $ad_expiry_date > time() ) {
553
- return true;
554
- }
555
-
556
- // set status to 'draft' if the ad is expired.
557
- if ( 'draft' !== $this->status ) {
558
- // removing the kses filters here so that expiring ads don’t lose HTML or other code.
559
- kses_remove_filters();
560
- wp_update_post(
561
- array(
562
- 'ID' => $this->id,
563
- 'post_status' => 'draft',
564
- )
565
- );
566
- kses_init_filters();
567
- /**
568
- * Run when an ad expires
569
- */
570
- do_action( 'advanced-ads-ad-expired', $this->id, $this );
571
- }
572
-
573
- return false;
574
  }
575
 
576
  /**
159
  * @var Advanced_Ads_Inline_Css
160
  */
161
  private $inline_css;
162
+ /**
163
+ * Timestamp if ad has an expiration date.
164
+ *
165
+ * @var int
166
+ */
167
+ public $expiry_date = 0;
168
+
169
+ /**
170
+ * The ad expiration object.
171
+ *
172
+ * @var Advanced_Ads_Ad_Expiration
173
+ */
174
+ private $ad_expiration;
175
 
176
  /**
177
  * Init ad object
251
  $this->description = $this->options( 'description' );
252
  $this->output = $this->options( 'output' );
253
  $this->status = $_data->post_status;
254
+ $this->expiry_date = (int) $this->options( 'expiry_date' );
255
  $this->is_head_placement = isset( $this->args['placement_type'] ) && 'header' === $this->args['placement_type'];
256
  $this->args['is_top_level'] = ! isset( $this->args['is_top_level'] );
257
 
272
  $this->wrapper['id'] = $this->create_wrapper_id();
273
  }
274
  }
275
+
276
+ $this->ad_expiration = new Advanced_Ads_Ad_Expiration( $this );
277
  }
278
 
279
  /**
459
  return false;
460
  }
461
 
462
+ if ( ! $this->can_display_by_visitor() ) {
 
 
 
 
463
  return false;
464
  }
465
+ } elseif ( 'publish' !== $this->status ) {
466
+ return false;
467
  }
468
 
469
+ if ( $this->ad_expiration->is_ad_expired() ) {
470
+ return false;
471
+ }
472
 
473
+ // add own conditions to flag output as possible or not.
474
+ return apply_filters( 'advanced-ads-can-display', true, $this, $check_options );
475
  }
476
 
477
  /**
558
  *
559
  * @return bool $can_display true if can be displayed in frontend based on expiry date
560
  * @since 1.3.15
561
+ * @deprecated 1.31.0 This is an internal method and should not have been public.
562
  */
563
  public function can_display_by_expiry_date() {
564
+ return $this->ad_expiration->is_ad_expired();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
565
  }
566
 
567
  /**
classes/ad_placements.php CHANGED
@@ -460,572 +460,7 @@ class Advanced_Ads_Placements {
460
  * @since 1.2.1
461
  */
462
  public static function &inject_in_content( $placement_id, $placement_opts, &$content ) {
463
- if ( ! extension_loaded( 'dom' ) ) {
464
- return $content;
465
- }
466
-
467
- // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
468
- // phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
469
-
470
- // get plugin options.
471
- $plugin_options = Advanced_Ads::get_instance()->options();
472
-
473
- $wp_charset = get_bloginfo( 'charset' );
474
- // parse document as DOM (fragment - having only a part of an actual post given).
475
-
476
- $content_to_load = self::get_content_to_load( $content, $wp_charset );
477
- if ( ! $content_to_load ) {
478
- return $content;
479
- }
480
-
481
- $dom = new DOMDocument( '1.0', $wp_charset );
482
- // may loose some fragments or add autop-like code.
483
- libxml_use_internal_errors( true ); // avoid notices and warnings - html is most likely malformed.
484
-
485
- $success = $dom->loadHtml( '<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=' . $wp_charset . '" /><body>' . $content_to_load );
486
- libxml_use_internal_errors( false );
487
- if ( true !== $success ) {
488
- // -TODO handle cases were dom-parsing failed (at least inform user)
489
- return $content;
490
- }
491
-
492
- // parse arguments.
493
- $tag = isset( $placement_opts['tag'] ) ? $placement_opts['tag'] : 'p';
494
- $tag = preg_replace( '/[^a-z0-9]/i', '', $tag ); // simplify tag.
495
- /**
496
- * Store the original tag value since $tag is changed on the fly and we might want to know the original selected
497
- * options for some checks later.
498
- */
499
- $tag_option = $tag;
500
-
501
- // allow more complex xPath expression.
502
- $tag = apply_filters( 'advanced-ads-placement-content-injection-xpath', $tag, $placement_opts );
503
-
504
- /**
505
- * Handle advanced tags.
506
- */
507
- switch ( $tag_option ) {
508
- case 'p':
509
- // exclude paragraphs within blockquote tags
510
- $tag = 'p[not(parent::blockquote)]';
511
- break;
512
- case 'pwithoutimg':
513
- // convert option name into correct path, exclude paragraphs within blockquote tags
514
- $tag = 'p[not(descendant::img) and not(parent::blockquote)]';
515
- break;
516
- case 'img':
517
- /*
518
- * Handle: 1) "img" tags 2) "image" block 3) "gallery" block 4) "gallery shortcode" 5) "wp_caption" shortcode
519
- * Handle the gallery created by the block or the shortcode as one image.
520
- * Prevent injection of ads next to images in tables.
521
- */
522
- // Default shortcodes, including non-HTML5 versions.
523
- $shortcodes = "@class and (
524
- contains(concat(' ', normalize-space(@class), ' '), ' gallery-size') or
525
- contains(concat(' ', normalize-space(@class), ' '), ' wp-caption ') )";
526
- $tag = "*[self::img or self::figure or self::div[$shortcodes]]
527
- [not(ancestor::table or ancestor::figure or ancestor::div[$shortcodes])]";
528
- break;
529
- // any headline. By default h2, h3, and h4
530
- case 'headlines':
531
- $headlines = apply_filters( 'advanced-ads-headlines-for-ad-injection', array( 'h2', 'h3', 'h4' ) );
532
-
533
- foreach ( $headlines as &$headline ) {
534
- $headline = 'self::' . $headline;
535
- }
536
- $tag = '*[' . implode( ' or ', $headlines ) . ']'; // /html/body/*[self::h2 or self::h3 or self::h4]
537
- break;
538
- // any HTML element that makes sense in the content
539
- case 'anyelement':
540
- $exclude = array(
541
- 'html',
542
- 'body',
543
- 'script',
544
- 'style',
545
- 'tr',
546
- 'td',
547
- // Inline tags.
548
- 'a',
549
- 'abbr',
550
- 'b',
551
- 'bdo',
552
- 'br',
553
- 'button',
554
- 'cite',
555
- 'code',
556
- 'dfn',
557
- 'em',
558
- 'i',
559
- 'img',
560
- 'kbd',
561
- 'label',
562
- 'option',
563
- 'q',
564
- 'samp',
565
- 'select',
566
- 'small',
567
- 'span',
568
- 'strong',
569
- 'sub',
570
- 'sup',
571
- 'textarea',
572
- 'time',
573
- 'tt',
574
- 'var',
575
- );
576
- $tag = '*[not(self::' . implode( ' or self::', $exclude ) . ')]';
577
- break;
578
- case 'custom':
579
- // get the path for the "custom" tag choice, use p as a fallback to prevent it from showing any ads if users left it empty
580
- $tag = ! empty( $placement_opts['xpath'] ) ? stripslashes( $placement_opts['xpath'] ) : 'p';
581
- break;
582
- }
583
-
584
- // select positions.
585
- $xpath = new DOMXPath( $dom );
586
- $items = $xpath->query( '/html/body/' . $tag );
587
-
588
- $options = array(
589
- 'allowEmpty' => false, // whether the tag can be empty to be counted.
590
- 'paragraph_select_from_bottom' => isset( $placement_opts['start_from_bottom'] ) && $placement_opts['start_from_bottom'],
591
- // only has before and after.
592
- 'before' => isset( $placement_opts['position'] ) && 'before' === $placement_opts['position'],
593
- );
594
-
595
- $options['paragraph_id'] = isset( $placement_opts['index'] ) ? $placement_opts['index'] : 1;
596
- $options['paragraph_id'] = max( 1, (int) $options['paragraph_id'] );
597
-
598
- // if there are too few items at this level test nesting.
599
- $options['itemLimit'] = 'p' === $tag_option ? 2 : 1;
600
-
601
- // trigger such a high item limit that all elements will be considered.
602
- if ( ! empty( $plugin_options['content-injection-level-disabled'] ) ) {
603
- $options['itemLimit'] = 1000;
604
- }
605
-
606
- // handle tags that are empty by definition or could be empty ("custom" option)
607
- if ( in_array( $tag_option, array( 'img', 'iframe', 'custom' ), true ) ) {
608
- $options['allowEmpty'] = true;
609
- }
610
-
611
- // allow hooks to change some options.
612
- $options = apply_filters(
613
- 'advanced-ads-placement-content-injection-options',
614
- $options,
615
- $tag_option
616
- );
617
-
618
- if ( $items->length < $options['itemLimit'] ) {
619
- $items = $xpath->query( '/html/body/*/' . $tag );
620
- }
621
- // try third level.
622
- if ( $items->length < $options['itemLimit'] ) {
623
- $items = $xpath->query( '/html/body/*/*/' . $tag );
624
- }
625
- // try all levels as last resort.
626
- if ( $items->length < $options['itemLimit'] ) {
627
- $items = $xpath->query( '//' . $tag );
628
- }
629
-
630
- // allow to select other elements.
631
- $items = apply_filters( 'advanced-ads-placement-content-injection-items', $items, $xpath, $tag_option );
632
-
633
- // filter empty tags from items.
634
- $whitespaces = json_decode( '"\t\n\r \u00A0"' );
635
- $paragraphs = array();
636
- foreach ( $items as $item ) {
637
- if ( $options['allowEmpty'] || ( isset( $item->textContent ) && trim( $item->textContent, $whitespaces ) !== '' ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
638
- $paragraphs[] = $item;
639
- }
640
- }
641
-
642
- $ancestors_to_limit = self::get_ancestors_to_limit( $xpath );
643
- $paragraphs = self::filter_by_ancestors_to_limit( $paragraphs, $ancestors_to_limit );
644
-
645
- $options['paragraph_count'] = count( $paragraphs );
646
-
647
- if ( $options['paragraph_count'] >= $options['paragraph_id'] ) {
648
- $offset = $options['paragraph_select_from_bottom'] ? $options['paragraph_count'] - $options['paragraph_id'] : $options['paragraph_id'] - 1;
649
- $offsets = apply_filters( 'advanced-ads-placement-content-offsets', array( $offset ), $options, $placement_opts, $xpath, $paragraphs, $dom );
650
- $did_inject = false;
651
-
652
- foreach ( $offsets as $offset ) {
653
-
654
- // inject.
655
- $node = apply_filters( 'advanced-ads-placement-content-injection-node', $paragraphs[ $offset ], $tag, $options['before'] );
656
-
657
- // Prevent injection into image caption and gallery.
658
- $parent = $node;
659
- for ( $i = 0; $i < 4; $i++ ) {
660
- $parent = $parent->parentNode;
661
- if ( ! $parent instanceof DOMElement ) {
662
- break;
663
- }
664
- if ( preg_match( '/\b(wp-caption|gallery-size)\b/', $parent->getAttribute( 'class' ) ) ) {
665
- $node = $parent;
666
- break;
667
- }
668
- }
669
-
670
- // make sure that the ad is injected outside the link
671
- if ( 'img' === $tag_option && 'a' === $node->parentNode->tagName ) {
672
- if ( $options['before'] ) {
673
- $node->parentNode;
674
- } else {
675
- // go one level deeper if inserted after to not insert the ad into the link; probably after the paragraph
676
- $node->parentNode->parentNode;
677
- }
678
- }
679
-
680
- $ad_content = Advanced_Ads_Select::get_instance()->get_ad_by_method( $placement_id, 'placement', $placement_opts );
681
-
682
- if ( trim( $ad_content, $whitespaces ) === '' ) {
683
- continue;
684
- }
685
-
686
- // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
687
- $ad_content = self::filter_ad_content( $ad_content, $node->tagName, $options );
688
-
689
- // convert HTML to XML!
690
- $ad_dom = new DOMDocument( '1.0', $wp_charset );
691
- libxml_use_internal_errors( true );
692
- $ad_dom->loadHtml( '<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=' . $wp_charset . '" /><body>' . $ad_content );
693
- // log errors.
694
- if ( defined( 'WP_DEBUG' ) && WP_DEBUG && current_user_can( 'advanced_ads_manage_options' ) ) {
695
- foreach ( libxml_get_errors() as $_error ) {
696
- // continue, if there is '&' symbol, but not HTML entity.
697
- if ( false === stripos( $_error->message, 'htmlParseEntityRef:' ) ) {
698
- Advanced_Ads::log( 'possible content injection error for placement "' . $placement_id . '": ' . print_r( $_error, true ) );
699
- }
700
- }
701
- }
702
-
703
- if ( $options['before'] ) {
704
- $ref_node = $node;
705
-
706
- foreach ( $ad_dom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
707
- $importedNode = $dom->importNode( $importedNode, true );
708
- $ref_node->parentNode->insertBefore( $importedNode, $ref_node );
709
- }
710
- } else {
711
- // append before next node or as last child to body.
712
- $ref_node = $node->nextSibling;
713
- if ( isset( $ref_node ) ) {
714
-
715
- foreach ( $ad_dom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
716
- $importedNode = $dom->importNode( $importedNode, true );
717
- $ref_node->parentNode->insertBefore( $importedNode, $ref_node );
718
- }
719
- } else {
720
- // append to body; -TODO using here that we only select direct children of the body tag.
721
- foreach ( $ad_dom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
722
- $importedNode = $dom->importNode( $importedNode, true );
723
- $node->parentNode->appendChild( $importedNode );
724
- }
725
- }
726
- }
727
-
728
- libxml_use_internal_errors( false );
729
- $did_inject = true;
730
- }
731
-
732
- if ( ! $did_inject ) {
733
- return $content;
734
- }
735
-
736
- $content_orig = $content;
737
- // convert to text-representation.
738
- $content = $dom->saveHTML();
739
- $content = self::prepare_output( $content, $content_orig );
740
-
741
- /**
742
- * Show a warning to ad admins in the Ad Health bar in the frontend, when
743
- *
744
- * * the level limitation was not disabled
745
- * * could not inject one ad (as by use of `elseif` here)
746
- * * but there are enough elements on the site, but just in sub-containers
747
- */
748
- } elseif ( current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options' ) )
749
- && empty( $plugin_options['content-injection-level-disabled'] ) ) {
750
-
751
- // Check if there are more elements without limitation.
752
- $all_items = $xpath->query( '//' . $tag );
753
-
754
- $paragraphs = array();
755
- foreach ( $all_items as $item ) {
756
- if ( $options['allowEmpty'] || ( isset( $item->textContent ) && trim( $item->textContent, $whitespaces ) !== '' ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
757
- $paragraphs[] = $item;
758
- }
759
- }
760
-
761
- $paragraphs = self::filter_by_ancestors_to_limit( $paragraphs, $ancestors_to_limit );
762
- if ( $options['paragraph_id'] <= count( $paragraphs ) ) {
763
- // Add a warning to ad health.
764
- add_filter( 'advanced-ads-ad-health-nodes', array( 'Advanced_Ads_Placements', 'add_ad_health_node' ) );
765
- }
766
- }
767
-
768
- // phpcs:enable
769
-
770
- return $content;
771
- }
772
-
773
- /**
774
- * Get content to load.
775
- *
776
- * @param string $content Original content.
777
- * @param string $wp_charset blog charset.
778
- *
779
- * @return string $content Content to load.
780
- */
781
- private static function get_content_to_load( $content, $wp_charset ) {
782
- $plugin_options = Advanced_Ads::get_instance()->options();
783
-
784
- // Prevent removing closing tags in scripts.
785
- $content_to_load = preg_replace( '/<script.*?<\/script>/si', '<!--\0-->', $content );
786
-
787
- // check which priority the wpautop filter has; might have been disabled on purpose.
788
- $wpautop_priority = has_filter( 'the_content', 'wpautop' );
789
- if ( $wpautop_priority && Advanced_Ads_Plugin::get_instance()->get_content_injection_priority() < $wpautop_priority ) {
790
- $content_to_load = wpautop( $content_to_load );
791
- }
792
-
793
- return $content_to_load;
794
- }
795
-
796
- /**
797
- * Filter ad content.
798
- *
799
- * @param string $ad_content Ad content.
800
- * @param string $tag_name tar before/after the content.
801
- * @param array $options Injection options.
802
- *
803
- * @return string ad content.
804
- */
805
- private static function filter_ad_content( $ad_content, $tag_name, $options ) {
806
- // Replace `</` with `<\/` in ad content when placed within `document.write()` to prevent code from breaking.
807
- $ad_content = preg_replace( '#(document.write.+)</(.*)#', '$1<\/$2', $ad_content );
808
-
809
- // Inject placeholder.
810
- $id = count( self::$ads_for_placeholders );
811
- self::$ads_for_placeholders[] = array(
812
- 'id' => $id,
813
- 'tag' => $tag_name,
814
- 'type' => $options['before'] ? 'before' : 'after',
815
- 'ad' => $ad_content,
816
- );
817
-
818
- return '%advads_placeholder_' . $id . '%';
819
- }
820
-
821
- /**
822
- * Prepare output.
823
- *
824
- * @param string $content Modified content.
825
- * @param string $content_orig Original content.
826
- *
827
- * @return string $content Content to output.
828
- */
829
- private static function prepare_output( $content, $content_orig ) {
830
- $plugin_options = Advanced_Ads::get_instance()->options();
831
-
832
- $content = self::inject_ads( $content, $content_orig, self::$ads_for_placeholders );
833
- self::$ads_for_placeholders = array();
834
-
835
- return $content;
836
- }
837
-
838
- /**
839
- * Search for ad placeholders in the `$content` to determine positions at which to inject ads.
840
- * Given the positions, inject ads into `$content_orig.
841
- *
842
- * @param string $content Post content with injected ad placeholders.
843
- * @param string $content_orig Unmodified post content.
844
- * @param array $options Injection options.
845
- * @param array $ads_for_placeholders Array of ads.
846
- * Each ad contains placeholder id, before or after which tag to inject the ad, the ad content.
847
- *
848
- * @return string $content
849
- */
850
- private static function inject_ads( $content, $content_orig, $ads_for_placeholders ) {
851
- $self_closing_tags = array(
852
- 'area',
853
- 'base',
854
- 'basefont',
855
- 'bgsound',
856
- 'br',
857
- 'col',
858
- 'embed',
859
- 'frame',
860
- 'hr',
861
- 'img',
862
- 'input',
863
- 'keygen',
864
- 'link',
865
- 'meta',
866
- 'param',
867
- 'source',
868
- 'track',
869
- 'wbr',
870
- );
871
-
872
- // It is not possible to append/prepend in self closing tags.
873
- foreach ( $ads_for_placeholders as &$ad_content ) {
874
- if ( ( 'prepend' === $ad_content['type'] || 'append' === $ad_content['type'] )
875
- && in_array( $ad_content['tag'], $self_closing_tags, true ) ) {
876
- $ad_content['type'] = 'after';
877
- }
878
- }
879
- unset( $ad_content );
880
- usort( $ads_for_placeholders, array( 'Advanced_Ads_Placements', 'sort_ads_for_placehoders' ) );
881
-
882
- // Add tags before/after which ad placehoders were injected.
883
- foreach ( $ads_for_placeholders as $ad_content ) {
884
- $tag = $ad_content['tag'];
885
-
886
- switch ( $ad_content['type'] ) {
887
- case 'before':
888
- case 'prepend':
889
- $alts[] = "<${tag}[^>]*>";
890
- break;
891
- case 'after':
892
- if ( in_array( $tag, $self_closing_tags, true ) ) {
893
- $alts[] = "<${tag}[^>]*>";
894
- } else {
895
- $alts[] = "</${tag}>";
896
- }
897
- break;
898
- case 'append':
899
- $alts[] = "</${tag}>";
900
- break;
901
- }
902
- }
903
- $alts = array_unique( $alts );
904
- $tag_regexp = implode( '|', $alts );
905
- // Add ad placeholder.
906
- $alts[] = '%advads_placeholder_(?:\d+)%';
907
- $tag_and_placeholder_regexp = implode( '|', $alts );
908
-
909
- preg_match_all( "#{$tag_and_placeholder_regexp}#i", $content, $tag_matches );
910
- $count = 0;
911
-
912
- // For each tag located before/after an ad placeholder, find its offset among the same tags.
913
- foreach ( $tag_matches[0] as $r ) {
914
- if ( preg_match( '/%advads_placeholder_(\d+)%/', $r, $result ) ) {
915
- $id = $result[1];
916
- $found_ad = false;
917
- foreach ( $ads_for_placeholders as $n => $ad ) {
918
- if ( (int) $ad['id'] === (int) $id ) {
919
- $found_ad = $ad;
920
- break;
921
- }
922
- }
923
- if ( ! $found_ad ) {
924
- continue;
925
- }
926
-
927
- switch ( $found_ad['type'] ) {
928
- case 'before':
929
- case 'append':
930
- $ads_for_placeholders[ $n ]['offset'] = $count;
931
- break;
932
- case 'after':
933
- case 'prepend':
934
- $ads_for_placeholders[ $n ]['offset'] = $count - 1;
935
- break;
936
- }
937
- } else {
938
- $count ++;
939
- }
940
- }
941
-
942
- // Find tags before/after which we need to inject ads.
943
- preg_match_all( "#{$tag_regexp}#i", $content_orig, $orig_tag_matches, PREG_OFFSET_CAPTURE );
944
- $new_content = '';
945
- $pos = 0;
946
-
947
- foreach ( $orig_tag_matches[0] as $n => $r ) {
948
- $to_inject = array();
949
- // Check if we need to inject an ad at this offset.
950
- foreach ( $ads_for_placeholders as $ad ) {
951
- if ( isset( $ad['offset'] ) && $ad['offset'] === $n ) {
952
- $to_inject[] = $ad;
953
- }
954
- }
955
-
956
- foreach ( $to_inject as $item ) {
957
- switch ( $item['type'] ) {
958
- case 'before':
959
- case 'append':
960
- $found_pos = $r[1];
961
- break;
962
- case 'after':
963
- case 'prepend':
964
- $found_pos = $r[1] + strlen( $r[0] );
965
- break;
966
- }
967
-
968
- $new_content .= substr( $content_orig, $pos, $found_pos - $pos );
969
- $pos = $found_pos;
970
- $new_content .= $item['ad'];
971
- }
972
- }
973
- $new_content .= substr( $content_orig, $pos );
974
-
975
- return $new_content;
976
- }
977
-
978
-
979
- /**
980
- * Callback function for usort() to sort ads for placeholders.
981
- *
982
- * @param array $first The first array to compare.
983
- * @param array $second The second array to compare.
984
- *
985
- * @return int 0 if both objects equal. -1 if second array should come first, 1 otherwise.
986
- */
987
- public static function sort_ads_for_placehoders( $first, $second ) {
988
- if ( $first['type'] === $second['type'] ) {
989
- return 0;
990
- }
991
-
992
- $num = array(
993
- 'before' => 1,
994
- 'prepend' => 2,
995
- 'append' => 3,
996
- 'after' => 4,
997
- );
998
-
999
- return $num[ $first['type'] ] > $num[ $second['type'] ] ? 1 : - 1;
1000
- }
1001
-
1002
- /**
1003
- * Add a warning to 'Ad health'.
1004
- *
1005
- * @param array $nodes .
1006
- *
1007
- * @return array $nodes.
1008
- */
1009
- public static function add_ad_health_node( $nodes ) {
1010
- $nodes[] = array(
1011
- 'type' => 1,
1012
- 'data' => array(
1013
- 'parent' => 'advanced_ads_ad_health',
1014
- 'id' => 'advanced_ads_ad_health_the_content_not_enough_elements',
1015
- 'title' => sprintf(
1016
- /* translators: %s stands for the name of the "Disable level limitation" option and automatically translated as well */
1017
- __( 'Set <em>%s</em> to show more ads', 'advanced-ads' ),
1018
- __( 'Disable level limitation', 'advanced-ads' )
1019
- ),
1020
- 'href' => admin_url( '/admin.php?page=advanced-ads-settings#top#general' ),
1021
- 'meta' => array(
1022
- 'class' => 'advanced_ads_ad_health_warning',
1023
- 'target' => '_blank',
1024
- ),
1025
- ),
1026
- );
1027
-
1028
- return $nodes;
1029
  }
1030
 
1031
  /**
460
  * @since 1.2.1
461
  */
462
  public static function &inject_in_content( $placement_id, $placement_opts, &$content ) {
463
+ return Advanced_Ads_In_Content_Injector::inject_in_content( $placement_id, $placement_opts, $content );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  }
465
 
466
  /**
classes/in-content-injector.php ADDED
@@ -0,0 +1,758 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Injects ads in the content based on an XPath expression.
4
+ */
5
+ class Advanced_Ads_In_Content_Injector {
6
+
7
+ /**
8
+ * Gather placeholders which later are replaced by the ads
9
+ *
10
+ * @var array $ads_for_placeholders
11
+ */
12
+ private static $ads_for_placeholders = array();
13
+
14
+ /**
15
+ * Inject ads directly into the content
16
+ *
17
+ * @param string $placement_id Id of the placement.
18
+ * @param array $placement_opts Placement options.
19
+ * @param string $content Content to inject placement into.
20
+ * @param array $options {
21
+ * Injection options.
22
+ *
23
+ * @type bool $allowEmpty Whether the tag can be empty to be counted.
24
+ * @type bool $paragraph_select_from_bottom Whether to select ads from buttom.
25
+ * @type string $position Position. Can be one of 'before', 'after', 'append', 'prepend'
26
+ * @type number $alter_nodes Whether to alter nodes, for example to prevent injecting ads into `a` tags.
27
+ * @type bool $repeat Whether to repeat the position.
28
+ * @type number $paragraph_id Paragraph Id.
29
+ * @type number $itemLimit If there are too few items at this level test nesting. Set to '-1` to prevent testing.
30
+ * }
31
+ *
32
+ * @return string $content Content with injected placement.
33
+ */
34
+ public static function &inject_in_content( $placement_id, $placement_opts, &$content, $options = array() ) {
35
+ if ( ! extension_loaded( 'dom' ) ) {
36
+ return $content;
37
+ }
38
+
39
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
40
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
41
+
42
+ // parse arguments.
43
+ $tag = isset( $placement_opts['tag'] ) ? $placement_opts['tag'] : 'p';
44
+ $tag = preg_replace( '/[^a-z0-9]/i', '', $tag ); // simplify tag.
45
+ /**
46
+ * Store the original tag value since $tag is changed on the fly and we might want to know the original selected
47
+ * options for some checks later.
48
+ */
49
+ $tag_option = $tag;
50
+
51
+ // allow more complex xPath expression.
52
+ $tag = apply_filters( 'advanced-ads-placement-content-injection-xpath', $tag, $placement_opts );
53
+
54
+ // get plugin options.
55
+ $plugin_options = Advanced_Ads::get_instance()->options();
56
+
57
+ $defaults = array(
58
+ 'allowEmpty' => false,
59
+ 'paragraph_select_from_bottom' => isset( $placement_opts['start_from_bottom'] ) && $placement_opts['start_from_bottom'],
60
+ 'position' => isset( $placement_opts['position'] ) ? $placement_opts['position'] : 'after',
61
+ // only has before and after.
62
+ 'before' => isset( $placement_opts['position'] ) && 'before' === $placement_opts['position'],
63
+ // Whether to alter nodes, for example to prevent injecting ads into `a` tags.
64
+ 'alter_nodes' => true,
65
+ 'repeat' => false,
66
+ );
67
+
68
+ $defaults['paragraph_id'] = isset( $placement_opts['index'] ) ? $placement_opts['index'] : 1;
69
+ $defaults['paragraph_id'] = max( 1, (int) $defaults['paragraph_id'] );
70
+
71
+ // if there are too few items at this level test nesting.
72
+ $defaults['itemLimit'] = 'p' === $tag_option ? 2 : 1;
73
+
74
+ // trigger such a high item limit that all elements will be considered.
75
+ if ( ! empty( $plugin_options['content-injection-level-disabled'] ) ) {
76
+ $defaults['itemLimit'] = 1000;
77
+ }
78
+
79
+ // handle tags that are empty by definition or could be empty ("custom" option)
80
+ if ( in_array( $tag_option, array( 'img', 'iframe', 'custom' ), true ) ) {
81
+ $defaults['allowEmpty'] = true;
82
+ }
83
+
84
+ // allow hooks to change some options.
85
+ $options = apply_filters(
86
+ 'advanced-ads-placement-content-injection-options',
87
+ wp_parse_args( $options, $defaults ),
88
+ $tag_option
89
+ );
90
+
91
+ $wp_charset = get_bloginfo( 'charset' );
92
+ // parse document as DOM (fragment - having only a part of an actual post given).
93
+
94
+ $content_to_load = self::get_content_to_load( $content, $wp_charset );
95
+ if ( ! $content_to_load ) {
96
+ return $content;
97
+ }
98
+
99
+ $dom = new DOMDocument( '1.0', $wp_charset );
100
+ // may loose some fragments or add autop-like code.
101
+ $libxml_use_internal_errors = libxml_use_internal_errors( true ); // avoid notices and warnings - html is most likely malformed.
102
+
103
+ $success = $dom->loadHtml( '<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=' . $wp_charset . '" /><body>' . $content_to_load );
104
+ libxml_use_internal_errors( $libxml_use_internal_errors );
105
+ if ( true !== $success ) {
106
+ // -TODO handle cases were dom-parsing failed (at least inform user)
107
+ return $content;
108
+ }
109
+
110
+ /**
111
+ * Handle advanced tags.
112
+ */
113
+ switch ( $tag_option ) {
114
+ case 'p':
115
+ // exclude paragraphs within blockquote tags
116
+ $tag = 'p[not(parent::blockquote)]';
117
+ break;
118
+ case 'pwithoutimg':
119
+ // convert option name into correct path, exclude paragraphs within blockquote tags
120
+ $tag = 'p[not(descendant::img) and not(parent::blockquote)]';
121
+ break;
122
+ case 'img':
123
+ /*
124
+ * Handle: 1) "img" tags 2) "image" block 3) "gallery" block 4) "gallery shortcode" 5) "wp_caption" shortcode
125
+ * Handle the gallery created by the block or the shortcode as one image.
126
+ * Prevent injection of ads next to images in tables.
127
+ */
128
+ // Default shortcodes, including non-HTML5 versions.
129
+ $shortcodes = "@class and (
130
+ contains(concat(' ', normalize-space(@class), ' '), ' gallery-size') or
131
+ contains(concat(' ', normalize-space(@class), ' '), ' wp-caption ') )";
132
+ $tag = "*[self::img or self::figure or self::div[$shortcodes]]
133
+ [not(ancestor::table or ancestor::figure or ancestor::div[$shortcodes])]";
134
+ break;
135
+ // any headline. By default h2, h3, and h4
136
+ case 'headlines':
137
+ $headlines = apply_filters( 'advanced-ads-headlines-for-ad-injection', array( 'h2', 'h3', 'h4' ) );
138
+
139
+ foreach ( $headlines as &$headline ) {
140
+ $headline = 'self::' . $headline;
141
+ }
142
+ $tag = '*[' . implode( ' or ', $headlines ) . ']'; // /html/body/*[self::h2 or self::h3 or self::h4]
143
+ break;
144
+ // any HTML element that makes sense in the content
145
+ case 'anyelement':
146
+ $exclude = array(
147
+ 'html',
148
+ 'body',
149
+ 'script',
150
+ 'style',
151
+ 'tr',
152
+ 'td',
153
+ // Inline tags.
154
+ 'a',
155
+ 'abbr',
156
+ 'b',
157
+ 'bdo',
158
+ 'br',
159
+ 'button',
160
+ 'cite',
161
+ 'code',
162
+ 'dfn',
163
+ 'em',
164
+ 'i',
165
+ 'img',
166
+ 'kbd',
167
+ 'label',
168
+ 'option',
169
+ 'q',
170
+ 'samp',
171
+ 'select',
172
+ 'small',
173
+ 'span',
174
+ 'strong',
175
+ 'sub',
176
+ 'sup',
177
+ 'textarea',
178
+ 'time',
179
+ 'tt',
180
+ 'var',
181
+ );
182
+ $tag = '*[not(self::' . implode( ' or self::', $exclude ) . ')]';
183
+ break;
184
+ case 'custom':
185
+ // get the path for the "custom" tag choice, use p as a fallback to prevent it from showing any ads if users left it empty
186
+ $tag = ! empty( $placement_opts['xpath'] ) ? stripslashes( $placement_opts['xpath'] ) : 'p';
187
+ break;
188
+ }
189
+
190
+ // select positions.
191
+ $xpath = new DOMXPath( $dom );
192
+
193
+
194
+ if ( $options['itemLimit'] !== -1 ) {
195
+ $items = $xpath->query( '/html/body/' . $tag );
196
+
197
+ if ( $items->length < $options['itemLimit'] ) {
198
+ $items = $xpath->query( '/html/body/*/' . $tag );
199
+ }
200
+ // try third level.
201
+ if ( $items->length < $options['itemLimit'] ) {
202
+ $items = $xpath->query( '/html/body/*/*/' . $tag );
203
+ }
204
+ // try all levels as last resort.
205
+ if ( $items->length < $options['itemLimit'] ) {
206
+ $items = $xpath->query( '//' . $tag );
207
+ }
208
+ } else {
209
+ $items = $xpath->query( $tag );
210
+ }
211
+
212
+ // allow to select other elements.
213
+ $items = apply_filters( 'advanced-ads-placement-content-injection-items', $items, $xpath, $tag_option );
214
+
215
+ // filter empty tags from items.
216
+ $whitespaces = json_decode( '"\t\n\r \u00A0"' );
217
+ $paragraphs = array();
218
+ foreach ( $items as $item ) {
219
+ if ( $options['allowEmpty'] || ( isset( $item->textContent ) && trim( $item->textContent, $whitespaces ) !== '' ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
220
+ $paragraphs[] = $item;
221
+ }
222
+ }
223
+
224
+ $ancestors_to_limit = self::get_ancestors_to_limit( $xpath );
225
+ $paragraphs = self::filter_by_ancestors_to_limit( $paragraphs, $ancestors_to_limit );
226
+
227
+ $options['paragraph_count'] = count( $paragraphs );
228
+
229
+ if ( $options['paragraph_count'] >= $options['paragraph_id'] ) {
230
+ $offset = $options['paragraph_select_from_bottom'] ? $options['paragraph_count'] - $options['paragraph_id'] : $options['paragraph_id'] - 1;
231
+ $offsets = apply_filters( 'advanced-ads-placement-content-offsets', array( $offset ), $options, $placement_opts, $xpath, $paragraphs, $dom );
232
+ $did_inject = false;
233
+
234
+ foreach ( $offsets as $offset ) {
235
+
236
+ // inject.
237
+ $node = apply_filters( 'advanced-ads-placement-content-injection-node', $paragraphs[ $offset ], $tag, $options['before'] );
238
+
239
+ if ( $options['alter_nodes'] ) {
240
+ // Prevent injection into image caption and gallery.
241
+ $parent = $node;
242
+ for ( $i = 0; $i < 4; $i++ ) {
243
+ $parent = $parent->parentNode;
244
+ if ( ! $parent instanceof DOMElement ) {
245
+ break;
246
+ }
247
+ if ( preg_match( '/\b(wp-caption|gallery-size)\b/', $parent->getAttribute( 'class' ) ) ) {
248
+ $node = $parent;
249
+ break;
250
+ }
251
+ }
252
+
253
+ // make sure that the ad is injected outside the link
254
+ if ( 'img' === $tag_option && 'a' === $node->parentNode->tagName ) {
255
+ if ( $options['before'] ) {
256
+ $node->parentNode;
257
+ } else {
258
+ // go one level deeper if inserted after to not insert the ad into the link; probably after the paragraph
259
+ $node->parentNode->parentNode;
260
+ }
261
+ }
262
+ }
263
+
264
+ $ad_content = (string) Advanced_Ads_Select::get_instance()->get_ad_by_method( $placement_id, 'placement', $placement_opts );
265
+
266
+ if ( trim( $ad_content, $whitespaces ) === '' ) {
267
+ continue;
268
+ }
269
+
270
+ // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
271
+ $ad_content = self::filter_ad_content( $ad_content, $node->tagName, $options );
272
+
273
+ // convert HTML to XML!
274
+ $ad_dom = new DOMDocument( '1.0', $wp_charset );
275
+ $libxml_use_internal_errors = libxml_use_internal_errors( true );
276
+ $ad_dom->loadHtml( '<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=' . $wp_charset . '" /><body>' . $ad_content );
277
+ // log errors.
278
+ if ( defined( 'WP_DEBUG' ) && WP_DEBUG && current_user_can( 'advanced_ads_manage_options' ) ) {
279
+ foreach ( libxml_get_errors() as $_error ) {
280
+ // continue, if there is '&' symbol, but not HTML entity.
281
+ if ( false === stripos( $_error->message, 'htmlParseEntityRef:' ) ) {
282
+ Advanced_Ads::log( 'possible content injection error for placement "' . $placement_id . '": ' . print_r( $_error, true ) );
283
+ }
284
+ }
285
+ }
286
+
287
+ switch ( $options['position'] ) {
288
+ case 'append':
289
+ $ref_node = $node;
290
+
291
+ foreach ( $ad_dom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
292
+ $importedNode = $dom->importNode( $importedNode, true );
293
+ $ref_node->appendChild( $importedNode );
294
+ }
295
+ break;
296
+ case 'prepend':
297
+ $ref_node = $node;
298
+
299
+ foreach ( $ad_dom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
300
+ $importedNode = $dom->importNode( $importedNode, true );
301
+ $ref_node->insertBefore( $importedNode, $ref_node->firstChild );
302
+ }
303
+ break;
304
+ case 'before':
305
+ $ref_node = $node;
306
+
307
+ foreach ( $ad_dom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
308
+ $importedNode = $dom->importNode( $importedNode, true );
309
+ $ref_node->parentNode->insertBefore( $importedNode, $ref_node );
310
+ }
311
+ break;
312
+ case 'after':
313
+ default:
314
+ // append before next node or as last child to body.
315
+ $ref_node = $node->nextSibling;
316
+ if ( isset( $ref_node ) ) {
317
+ foreach ( $ad_dom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
318
+ $importedNode = $dom->importNode( $importedNode, true );
319
+ $ref_node->parentNode->insertBefore( $importedNode, $ref_node );
320
+ }
321
+ } else {
322
+ // append to body; -TODO using here that we only select direct children of the body tag.
323
+ foreach ( $ad_dom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
324
+ $importedNode = $dom->importNode( $importedNode, true );
325
+ $node->parentNode->appendChild( $importedNode );
326
+ }
327
+ }
328
+ }
329
+ libxml_use_internal_errors( $libxml_use_internal_errors );
330
+ $did_inject = true;
331
+ }
332
+
333
+ if ( ! $did_inject ) {
334
+ return $content;
335
+ }
336
+
337
+ $content_orig = $content;
338
+ // convert to text-representation.
339
+ $content = $dom->saveHTML();
340
+ $content = self::prepare_output( $content, $content_orig );
341
+
342
+ /**
343
+ * Show a warning to ad admins in the Ad Health bar in the frontend, when
344
+ *
345
+ * * the level limitation was not disabled
346
+ * * could not inject one ad (as by use of `elseif` here)
347
+ * * but there are enough elements on the site, but just in sub-containers
348
+ */
349
+ } elseif ( current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options' ) )
350
+ && $options['itemLimit'] !== -1
351
+ && empty( $plugin_options['content-injection-level-disabled'] ) ) {
352
+
353
+ // Check if there are more elements without limitation.
354
+ $all_items = $xpath->query( '//' . $tag );
355
+
356
+ $paragraphs = array();
357
+ foreach ( $all_items as $item ) {
358
+ if ( $options['allowEmpty'] || ( isset( $item->textContent ) && trim( $item->textContent, $whitespaces ) !== '' ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
359
+ $paragraphs[] = $item;
360
+ }
361
+ }
362
+
363
+ $paragraphs = self::filter_by_ancestors_to_limit( $paragraphs, $ancestors_to_limit );
364
+ if ( $options['paragraph_id'] <= count( $paragraphs ) ) {
365
+ // Add a warning to ad health.
366
+ add_filter( 'advanced-ads-ad-health-nodes', array( 'Advanced_Ads_In_Content_Injector', 'add_ad_health_node' ) );
367
+ }
368
+ }
369
+
370
+ // phpcs:enable
371
+
372
+ return $content;
373
+ }
374
+
375
+ /**
376
+ * Get content to load.
377
+ *
378
+ * @param string $content Original content.
379
+ * @param string $wp_charset blog charset.
380
+ *
381
+ * @return string $content Content to load.
382
+ */
383
+ private static function get_content_to_load( $content, $wp_charset ) {
384
+ // Prevent removing closing tags in scripts.
385
+ $content_to_load = preg_replace( '/<script.*?<\/script>/si', '<!--\0-->', $content );
386
+
387
+ // check which priority the wpautop filter has; might have been disabled on purpose.
388
+ $wpautop_priority = has_filter( 'the_content', 'wpautop' );
389
+ if ( $wpautop_priority && Advanced_Ads_Plugin::get_instance()->get_content_injection_priority() < $wpautop_priority ) {
390
+ $content_to_load = wpautop( $content_to_load );
391
+ }
392
+
393
+ return $content_to_load;
394
+ }
395
+
396
+ /**
397
+ * Filter ad content.
398
+ *
399
+ * @param string $ad_content Ad content.
400
+ * @param string $tag_name tar before/after the content.
401
+ * @param array $options Injection options.
402
+ *
403
+ * @return string ad content.
404
+ */
405
+ private static function filter_ad_content( $ad_content, $tag_name, $options ) {
406
+ // Replace `</` with `<\/` in ad content when placed within `document.write()` to prevent code from breaking.
407
+ $ad_content = preg_replace( '#(document.write.+)</(.*)#', '$1<\/$2', $ad_content );
408
+
409
+ // Inject placeholder.
410
+ $id = count( self::$ads_for_placeholders );
411
+ self::$ads_for_placeholders[] = array(
412
+ 'id' => $id,
413
+ 'tag' => $tag_name,
414
+ 'position' => $options['position'],
415
+ 'ad' => $ad_content,
416
+ );
417
+
418
+ return '%advads_placeholder_' . $id . '%';
419
+ }
420
+
421
+ /**
422
+ * Prepare output.
423
+ *
424
+ * @param string $content Modified content.
425
+ * @param string $content_orig Original content.
426
+ *
427
+ * @return string $content Content to output.
428
+ */
429
+ private static function prepare_output( $content, $content_orig ) {
430
+ $content = self::inject_ads( $content, $content_orig, self::$ads_for_placeholders );
431
+ self::$ads_for_placeholders = array();
432
+
433
+ return $content;
434
+ }
435
+
436
+ /**
437
+ * Search for ad placeholders in the `$content` to determine positions at which to inject ads.
438
+ * Given the positions, inject ads into `$content_orig.
439
+ *
440
+ * @param string $content Post content with injected ad placeholders.
441
+ * @param string $content_orig Unmodified post content.
442
+ * @param array $ads_for_placeholders Array of ads.
443
+ * Each ad contains placeholder id, before or after which tag to inject the ad, the ad content.
444
+ *
445
+ * @return string $content
446
+ */
447
+ private static function inject_ads( $content, $content_orig, $ads_for_placeholders ) {
448
+ $self_closing_tags = array(
449
+ 'area',
450
+ 'base',
451
+ 'basefont',
452
+ 'bgsound',
453
+ 'br',
454
+ 'col',
455
+ 'embed',
456
+ 'frame',
457
+ 'hr',
458
+ 'img',
459
+ 'input',
460
+ 'keygen',
461
+ 'link',
462
+ 'meta',
463
+ 'param',
464
+ 'source',
465
+ 'track',
466
+ 'wbr',
467
+ );
468
+
469
+ // It is not possible to append/prepend in self closing tags.
470
+ foreach ( $ads_for_placeholders as &$ad_content ) {
471
+ if ( ( 'prepend' === $ad_content['position'] || 'append' === $ad_content['position'] )
472
+ && in_array( $ad_content['tag'], $self_closing_tags, true ) ) {
473
+ $ad_content['position'] = 'after';
474
+ }
475
+ }
476
+ unset( $ad_content );
477
+ usort( $ads_for_placeholders, array( 'Advanced_Ads_In_Content_Injector', 'sort_ads_for_placehoders' ) );
478
+
479
+ // Add tags before/after which ad placehoders were injected.
480
+ foreach ( $ads_for_placeholders as $ad_content ) {
481
+ $tag = $ad_content['tag'];
482
+
483
+ switch ( $ad_content['position'] ) {
484
+ case 'before':
485
+ case 'prepend':
486
+ $alts[] = "<${tag}[^>]*>";
487
+ break;
488
+ case 'after':
489
+ if ( in_array( $tag, $self_closing_tags, true ) ) {
490
+ $alts[] = "<${tag}[^>]*>";
491
+ } else {
492
+ $alts[] = "</${tag}>";
493
+ }
494
+ break;
495
+ case 'append':
496
+ $alts[] = "</${tag}>";
497
+ break;
498
+ }
499
+ }
500
+ $alts = array_unique( $alts );
501
+ $tag_regexp = implode( '|', $alts );
502
+ // Add ad placeholder.
503
+ $alts[] = '%advads_placeholder_(?:\d+)%';
504
+ $tag_and_placeholder_regexp = implode( '|', $alts );
505
+
506
+ preg_match_all( "#{$tag_and_placeholder_regexp}#i", $content, $tag_matches );
507
+ $count = 0;
508
+
509
+ // For each tag located before/after an ad placeholder, find its offset among the same tags.
510
+ foreach ( $tag_matches[0] as $r ) {
511
+ if ( preg_match( '/%advads_placeholder_(\d+)%/', $r, $result ) ) {
512
+ $id = $result[1];
513
+ $found_ad = false;
514
+ foreach ( $ads_for_placeholders as $n => $ad ) {
515
+ if ( (int) $ad['id'] === (int) $id ) {
516
+ $found_ad = $ad;
517
+ break;
518
+ }
519
+ }
520
+ if ( ! $found_ad ) {
521
+ continue;
522
+ }
523
+
524
+ switch ( $found_ad['position'] ) {
525
+ case 'before':
526
+ case 'append':
527
+ $ads_for_placeholders[ $n ]['offset'] = $count;
528
+ break;
529
+ case 'after':
530
+ case 'prepend':
531
+ $ads_for_placeholders[ $n ]['offset'] = $count - 1;
532
+ break;
533
+ }
534
+ } else {
535
+ $count ++;
536
+ }
537
+ }
538
+
539
+ // Find tags before/after which we need to inject ads.
540
+ preg_match_all( "#{$tag_regexp}#i", $content_orig, $orig_tag_matches, PREG_OFFSET_CAPTURE );
541
+ $new_content = '';
542
+ $pos = 0;
543
+
544
+ foreach ( $orig_tag_matches[0] as $n => $r ) {
545
+ $to_inject = array();
546
+ // Check if we need to inject an ad at this offset.
547
+ foreach ( $ads_for_placeholders as $ad ) {
548
+ if ( isset( $ad['offset'] ) && $ad['offset'] === $n ) {
549
+ $to_inject[] = $ad;
550
+ }
551
+ }
552
+
553
+ foreach ( $to_inject as $item ) {
554
+ switch ( $item['position'] ) {
555
+ case 'before':
556
+ case 'append':
557
+ $found_pos = $r[1];
558
+ break;
559
+ case 'after':
560
+ case 'prepend':
561
+ $found_pos = $r[1] + strlen( $r[0] );
562
+ break;
563
+ }
564
+
565
+ $new_content .= substr( $content_orig, $pos, $found_pos - $pos );
566
+ $pos = $found_pos;
567
+ $new_content .= $item['ad'];
568
+ }
569
+ }
570
+ $new_content .= substr( $content_orig, $pos );
571
+
572
+ return $new_content;
573
+ }
574
+
575
+
576
+ /**
577
+ * Callback function for usort() to sort ads for placeholders.
578
+ *
579
+ * @param array $first The first array to compare.
580
+ * @param array $second The second array to compare.
581
+ *
582
+ * @return int 0 if both objects equal. -1 if second array should come first, 1 otherwise.
583
+ */
584
+ public static function sort_ads_for_placehoders( $first, $second ) {
585
+ if ( $first['position'] === $second['position'] ) {
586
+ return 0;
587
+ }
588
+
589
+ $num = array(
590
+ 'before' => 1,
591
+ 'prepend' => 2,
592
+ 'append' => 3,
593
+ 'after' => 4,
594
+ );
595
+
596
+ return $num[ $first['position'] ] > $num[ $second['position'] ] ? 1 : - 1;
597
+ }
598
+
599
+ /**
600
+ * Add a warning to 'Ad health'.
601
+ *
602
+ * @param array $nodes .
603
+ *
604
+ * @return array $nodes.
605
+ */
606
+ public static function add_ad_health_node( $nodes ) {
607
+ $nodes[] = array(
608
+ 'type' => 1,
609
+ 'data' => array(
610
+ 'parent' => 'advanced_ads_ad_health',
611
+ 'id' => 'advanced_ads_ad_health_the_content_not_enough_elements',
612
+ 'title' => sprintf(
613
+ /* translators: %s stands for the name of the "Disable level limitation" option and automatically translated as well */
614
+ __( 'Set <em>%s</em> to show more ads', 'advanced-ads' ),
615
+ __( 'Disable level limitation', 'advanced-ads' )
616
+ ),
617
+ 'href' => admin_url( '/admin.php?page=advanced-ads-settings#top#general' ),
618
+ 'meta' => array(
619
+ 'class' => 'advanced_ads_ad_health_warning',
620
+ 'target' => '_blank',
621
+ ),
622
+ ),
623
+ );
624
+
625
+ return $nodes;
626
+ }
627
+
628
+ /**
629
+ * Get paths of ancestors that should not contain ads.
630
+ *
631
+ * @param object $xpath DOMXPath object.
632
+ *
633
+ * @return array Paths of ancestors.
634
+ */
635
+ private static function get_ancestors_to_limit( $xpath ) {
636
+ $query = self::get_ancestors_to_limit_query();
637
+ if ( ! $query ) {
638
+ return array();
639
+ }
640
+
641
+ $node_list = $xpath->query( $query );
642
+ $ancestors_to_limit = array();
643
+
644
+ foreach ( $node_list as $a ) {
645
+ $ancestors_to_limit[] = $a->getNodePath();
646
+ }
647
+
648
+ return $ancestors_to_limit;
649
+ }
650
+
651
+
652
+ /**
653
+ * Remove paragraphs that has ancestors that should not contain ads.
654
+ *
655
+ * @param array $paragraphs An array of `DOMNode` objects to insert ads before or after.
656
+ * @param array $ancestors_to_limit Paths of ancestor that should not contain ads.
657
+ *
658
+ * @return array $new_paragraphs An array of `DOMNode` objects to insert ads before or after.
659
+ */
660
+ private static function filter_by_ancestors_to_limit( $paragraphs, $ancestors_to_limit ) {
661
+ $new_paragraphs = array();
662
+
663
+ foreach ( $paragraphs as $k => $paragraph ) {
664
+ foreach ( $ancestors_to_limit as $a ) {
665
+ if ( 0 === stripos( $paragraph->getNodePath(), $a ) ) {
666
+ continue 2;
667
+ }
668
+ }
669
+
670
+ $new_paragraphs[] = $paragraph;
671
+ }
672
+
673
+ return $new_paragraphs;
674
+ }
675
+
676
+ /**
677
+ * Get query to select ancestors that should not contain ads.
678
+ *
679
+ * @return string/false DOMXPath query or false.
680
+ */
681
+ private static function get_ancestors_to_limit_query() {
682
+ /**
683
+ * TODO:
684
+ * - support `%` (rand) at the start
685
+ * - support plain text that node should contain instead of CSS selectors
686
+ * - support `prev` and `next` as `type`
687
+ */
688
+
689
+ /**
690
+ * Filter the nodes that limit injection.
691
+ *
692
+ * @param array An array of arrays, each of which contains:
693
+ *
694
+ * @type string $type Accept: `ancestor` - limit injection inside the ancestor.
695
+ * @type string $node A "class selector" which targets one class (.) or "id selector" which targets one id (#),
696
+ * optionally with `%` at the end.
697
+ */
698
+ $items = apply_filters(
699
+ 'advanced-ads-content-injection-nodes-without-ads',
700
+ array(
701
+ array(
702
+ // a class anyone can use to prevent automatic ad injection into a specific element.
703
+ 'node' => '.advads-stop-injection',
704
+ 'type' => 'ancestor',
705
+ ),
706
+ array(
707
+ // Product Slider for Beaver Builder by WooPack.
708
+ 'node' => '.woopack-product-carousel',
709
+ 'type' => 'ancestor',
710
+ ),
711
+ array(
712
+ // WP Author Box Lite.
713
+ 'node' => '#wpautbox-%',
714
+ 'type' => 'ancestor',
715
+ ),
716
+ array(
717
+ // GeoDirectory Post Slider.
718
+ 'node' => '.geodir-post-slider',
719
+ 'type' => 'ancestor',
720
+ ),
721
+ )
722
+ );
723
+
724
+ $query = array();
725
+ foreach ( $items as $p ) {
726
+ $sel = $p['node'];
727
+
728
+ $sel_type = substr( $sel, 0, 1 );
729
+ $sel = substr( $sel, 1 );
730
+
731
+ $rand_pos = strpos( $sel, '%' );
732
+ $sel = str_replace( '%', '', $sel );
733
+ $sel = sanitize_html_class( $sel );
734
+
735
+ if ( '.' === $sel_type ) {
736
+ if ( false !== $rand_pos ) {
737
+ $query[] = "@class and contains(concat(' ', normalize-space(@class), ' '), ' $sel')";
738
+ } else {
739
+ $query[] = "@class and contains(concat(' ', normalize-space(@class), ' '), ' $sel ')";
740
+ }
741
+ }
742
+ if ( '#' === $sel_type ) {
743
+ if ( false !== $rand_pos ) {
744
+ $query[] = "@id and starts-with(@id, '$sel')";
745
+ } else {
746
+ $query[] = "@id and @id = '$sel'";
747
+ }
748
+ }
749
+ }
750
+
751
+ if ( ! $query ) {
752
+ return false;
753
+ }
754
+
755
+ return '//*[' . implode( ' or ', $query ) . ']';
756
+ }
757
+
758
+ }
classes/plugin.php CHANGED
@@ -114,15 +114,13 @@ class Advanced_Ads_Plugin {
114
  add_action( 'widgets_init', array( $this, 'widget_init' ) );
115
 
116
  // Call action hooks for ad status changes.
117
- add_action(
118
- 'transition_post_status',
119
- array(
120
- $this,
121
- 'transition_ad_status',
122
- ),
123
- 10,
124
- 3
125
- );
126
 
127
  // load display conditions.
128
  Advanced_Ads_Display_Conditions::get_instance();
@@ -895,6 +893,16 @@ class Advanced_Ads_Plugin {
895
  */
896
  do_action( 'advanced-ads-ad-status-unpublished', $ad );
897
  }
 
 
 
 
 
 
 
 
 
 
898
  }
899
 
900
  }
114
  add_action( 'widgets_init', array( $this, 'widget_init' ) );
115
 
116
  // Call action hooks for ad status changes.
117
+ add_action( 'transition_post_status', array( $this, 'transition_ad_status' ), 10, 3 );
118
+
119
+ // register expired post status.
120
+ Advanced_Ads_Ad_Expiration::register_post_status();
121
+
122
+ // if expired ad gets untrashed, revert it to expired status (instead of draft).
123
+ add_filter( 'wp_untrash_post_status', array( Advanced_Ads_Ad_Expiration::class, 'wp_untrash_post_status' ), 10, 3 );
 
 
124
 
125
  // load display conditions.
126
  Advanced_Ads_Display_Conditions::get_instance();
893
  */
894
  do_action( 'advanced-ads-ad-status-unpublished', $ad );
895
  }
896
+
897
+ if ( $old_status === 'publish' && $new_status === Advanced_Ads_Ad_Expiration::POST_STATUS ) {
898
+ /**
899
+ * Fires when an ad is expired.
900
+ *
901
+ * @param int $id
902
+ * @param Advanced_Ads_Ad $ad
903
+ */
904
+ do_action( 'advanced-ads-ad-expired', $ad->id, $ad );
905
+ }
906
  }
907
 
908
  }
languages/advanced-ads.pot CHANGED
@@ -1,15 +1,15 @@
1
- # Copyright (C) 2021 Thomas Maier, Advanced Ads GmbH
2
  # This file is distributed under the same license as the Advanced Ads plugin.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: Advanced Ads 1.30.5\n"
6
  "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/advanced-ads/\n"
7
  "Last-Translator: Thomas Maier <post@webzunft.de>\n"
8
  "Language-Team: webgilde <support@wpadvancedads.com>\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=UTF-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
12
- "POT-Creation-Date: 2021-12-16T09:41:32+01:00\n"
13
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14
  "X-Generator: WP-CLI 2.4.0\n"
15
  "X-Domain: advanced-ads\n"
@@ -108,7 +108,7 @@ msgstr ""
108
 
109
  #. translators: %s is the URL to add a new review, https://wordpress.org/support/plugin/advanced-ads/reviews/#new-post
110
  #. translators: %s is a URL.
111
- #: admin/class-advanced-ads-admin.php:664
112
  #: admin/includes/class-overview-widgets.php:191
113
  msgid "Thank the developer with a &#9733;&#9733;&#9733;&#9733;&#9733; review on <a href=\"%s\" target=\"_blank\">wordpress.org</a>"
114
  msgstr ""
@@ -126,7 +126,7 @@ msgstr ""
126
  #. translators: %s is a list of PHP extensions.
127
  #. Translators: %s is a name of a module.
128
  #: admin/includes/ad-health-notices.php:54
129
- #: admin/views/placements.php:388
130
  msgid "Missing PHP extensions could cause issues. Please ask your hosting provider to enable them: %s"
131
  msgstr ""
132
 
@@ -228,7 +228,6 @@ msgstr ""
228
  msgid "%s detected."
229
  msgstr ""
230
 
231
- #. translators: %s is a service or plugin name.
232
  #: admin/includes/ad-health-notices.php:253
233
  msgid "Learn how this might impact your ad setup."
234
  msgstr ""
@@ -312,7 +311,7 @@ msgid "Display ads with the highest ad weight first"
312
  msgstr ""
313
 
314
  #: admin/includes/class-ad-groups-list.php:339
315
- #: admin/views/placements.php:275
316
  #: modules/import-export/classes/import.php:153
317
  #: modules/import-export/classes/import.php:193
318
  #: modules/import-export/classes/import.php:595
@@ -321,7 +320,7 @@ msgid "Edit"
321
  msgstr ""
322
 
323
  #: admin/includes/class-ad-groups-list.php:340
324
- #: admin/views/placements.php:306
325
  msgid "Usage"
326
  msgstr ""
327
 
@@ -344,99 +343,99 @@ msgstr ""
344
  msgid "No ad group created"
345
  msgstr ""
346
 
347
- #: admin/includes/class-ad-type.php:269
348
- #: admin/includes/class-ad-type.php:275
349
  msgid "Ad Details"
350
  msgstr ""
351
 
352
- #: admin/includes/class-ad-type.php:270
353
- #: admin/includes/class-ad-type.php:276
354
  msgid "Ad Planning"
355
  msgstr ""
356
 
357
- #: admin/includes/class-ad-type.php:271
358
- #: admin/includes/class-ad-type.php:277
359
  msgid "Ad Shortcode"
360
  msgstr ""
361
 
362
  #. translators: %s is the number of ads.
363
- #: admin/includes/class-ad-type.php:441
364
  msgid "%s ad updated."
365
  msgid_plural "%s ads updated."
366
  msgstr[0] ""
367
  msgstr[1] ""
368
 
369
  #. translators: %s is the number of ads.
370
- #: admin/includes/class-ad-type.php:443
371
  msgid "%s ad not updated, somebody is editing it."
372
  msgid_plural "%s ads not updated, somebody is editing them."
373
  msgstr[0] ""
374
  msgstr[1] ""
375
 
376
  #. translators: %s is the number of ads.
377
- #: admin/includes/class-ad-type.php:445
378
  msgid "%s ad permanently deleted."
379
  msgid_plural "%s ads permanently deleted."
380
  msgstr[0] ""
381
  msgstr[1] ""
382
 
383
  #. translators: %s is the number of ads.
384
- #: admin/includes/class-ad-type.php:447
385
  msgid "%s ad moved to the Trash."
386
  msgid_plural "%s ads moved to the Trash."
387
  msgstr[0] ""
388
  msgstr[1] ""
389
 
390
  #. translators: %s is the number of ads.
391
- #: admin/includes/class-ad-type.php:449
392
  msgid "%s ad restored from the Trash."
393
  msgid_plural "%s ads restored from the Trash."
394
  msgstr[0] ""
395
  msgstr[1] ""
396
 
397
  #. Translators: %s is the time the ad was first saved.
398
- #: admin/includes/class-ad-type.php:662
399
  msgid "Ad created on %s"
400
  msgstr ""
401
 
402
- #: admin/includes/class-ad-type.php:854
403
- #: admin/includes/class-ad-type.php:855
404
  msgid "Ad updated."
405
  msgstr ""
406
 
407
  #. translators: %s: date and time of the revision
408
- #: admin/includes/class-ad-type.php:856
409
  msgid "Ad restored to revision from %s"
410
  msgstr ""
411
 
412
- #: admin/includes/class-ad-type.php:857
413
- #: admin/includes/class-ad-type.php:858
414
  msgid "Ad saved."
415
  msgstr ""
416
 
417
- #: admin/includes/class-ad-type.php:859
418
  msgid "Ad submitted."
419
  msgstr ""
420
 
421
  #. translators: %1$s is a date.
422
- #: admin/includes/class-ad-type.php:862
423
  msgid "Ad scheduled for: <strong>%1$s</strong>."
424
  msgstr ""
425
 
426
  #. translators: Publish box date format, see http://php.net/date.
427
- #: admin/includes/class-ad-type.php:864
428
  msgid "M j, Y @ G:i"
429
  msgstr ""
430
 
431
- #: admin/includes/class-ad-type.php:866
432
  msgid "Ad draft updated."
433
  msgstr ""
434
 
435
- #: admin/includes/class-ad-type.php:922
436
  msgid "You don’t have access to ads. Please deactivate and re-enable Advanced Ads again to fix this."
437
  msgstr ""
438
 
439
- #: admin/includes/class-ad-type.php:923
440
  #: classes/frontend_checks.php:503
441
  msgid "Get help"
442
  msgstr ""
@@ -480,71 +479,78 @@ msgstr ""
480
  msgid "Please enter a valid license key"
481
  msgstr ""
482
 
483
- #: admin/includes/class-licenses.php:187
 
 
 
 
 
484
  msgid "License couldn’t be activated. Please try again later."
485
  msgstr ""
486
 
487
- #: admin/includes/class-licenses.php:204
488
  msgid "This is the bundle license key."
489
  msgstr ""
490
 
491
- #: admin/includes/class-licenses.php:205
492
  msgid "This is not the correct key for this add-on."
493
  msgstr ""
494
 
495
- #: admin/includes/class-licenses.php:206
496
  msgid "There are no activations left."
497
  msgstr ""
498
 
499
  #. translators: %1$s is a starting link tag, %2$s is the closing one.
500
- #: admin/includes/class-licenses.php:210
501
  msgid "You can manage activations in %1$syour account%2$s."
502
  msgstr ""
503
 
504
  #. translators: %1$s is a starting link tag, %2$s is the closing one.
505
- #: admin/includes/class-licenses.php:216
506
  msgid "%1$sUpgrade%2$s for more activations."
507
  msgstr ""
508
 
509
  #. translators: %s is a string containing information about the issue.
510
- #: admin/includes/class-licenses.php:230
511
  msgid "License is invalid. Reason: %s"
512
  msgstr ""
513
 
514
  #. translators: %s is a list of server information like IP address. Just keep it as is.
515
- #: admin/includes/class-licenses.php:270
516
  msgid "Your request was blocked by our firewall. Please send us the following information to unblock you: %s."
517
  msgstr ""
518
 
519
- #: admin/includes/class-licenses.php:332
520
  msgid "Error while trying to disable the license. Please contact support."
521
  msgstr ""
522
 
523
- #: admin/includes/class-licenses.php:367
524
- #: admin/includes/class-licenses.php:390
525
  msgid "License couldn’t be deactivated. Please try again later."
526
  msgstr ""
527
 
528
- #: admin/includes/class-licenses.php:624
529
  msgid "Download failed. <a href=\"%s\">Click here to try another method</a>."
530
  msgstr ""
531
 
532
- #: admin/includes/class-licenses.php:626
533
  msgid "Download failed. <a href=\"%s\" target=\"_blank\">Click here to learn why</a>."
534
  msgstr ""
535
 
536
- #: admin/includes/class-list-filters.php:142
537
  #: modules/gadsense/admin/admin.php:73
538
  #: modules/gadsense/admin/views/adsense-ad-parameters.php:109
539
  msgid "Responsive"
540
  msgstr ""
541
 
542
- #: admin/includes/class-list-filters.php:151
543
- msgid "expired"
 
544
  msgstr ""
545
 
546
- #: admin/includes/class-list-filters.php:155
547
- msgid "any expiry date"
 
548
  msgstr ""
549
 
550
  #: admin/includes/class-menu.php:66
@@ -644,14 +650,14 @@ msgstr ""
644
 
645
  #: admin/includes/class-meta-box.php:104
646
  #: admin/views/placements.php:184
647
- #: admin/views/placements.php:429
648
  #: classes/ad-debug.php:152
649
  msgid "Display Conditions"
650
  msgstr ""
651
 
652
  #: admin/includes/class-meta-box.php:112
653
  #: admin/views/placements.php:193
654
- #: admin/views/placements.php:437
655
  #: classes/ad-debug.php:239
656
  msgid "Visitor Conditions"
657
  msgstr ""
@@ -745,8 +751,8 @@ msgid "Create your first ad"
745
  msgstr ""
746
 
747
  #: admin/includes/class-overview-widgets.php:118
748
- #: modules/gadsense/admin/views/adsense-account.php:73
749
- #: modules/gadsense/admin/views/adsense-account.php:95
750
  msgid "Connect to AdSense"
751
  msgstr ""
752
 
@@ -957,7 +963,7 @@ msgid "Priority of content injection filter"
957
  msgstr ""
958
 
959
  #: admin/includes/class-settings.php:178
960
- #: classes/ad_placements.php:1018
961
  msgid "Disable level limitation"
962
  msgstr ""
963
 
@@ -1171,7 +1177,7 @@ msgid "Update"
1171
  msgstr ""
1172
 
1173
  #: admin/views/ad-group-list-ads.php:20
1174
- #: admin/views/placements.php:347
1175
  #: classes/ad-debug.php:118
1176
  #: classes/ad-debug.php:167
1177
  #: classes/ad-debug.php:169
@@ -1198,7 +1204,7 @@ msgstr ""
1198
 
1199
  #: admin/views/ad-group-list-form-row.php:56
1200
  #: admin/views/placements.php:64
1201
- #: admin/views/placements.php:257
1202
  #: modules/gadsense/admin/views/adsense-ad-parameters.php:105
1203
  msgid "Type"
1204
  msgstr ""
@@ -1227,7 +1233,7 @@ msgstr ""
1227
 
1228
  #: admin/views/ad-group-list-row.php:22
1229
  #: admin/views/ad-info.php:7
1230
- #: admin/views/placements.php:310
1231
  msgid "shortcode"
1232
  msgstr ""
1233
 
@@ -1354,10 +1360,6 @@ msgid "all ad sizes"
1354
  msgstr ""
1355
 
1356
  #: admin/views/ad-list-filters.php:34
1357
- msgid "all ad dates"
1358
- msgstr ""
1359
-
1360
- #: admin/views/ad-list-filters.php:42
1361
  msgid "all ad groups"
1362
  msgstr ""
1363
 
@@ -1387,7 +1389,7 @@ msgstr ""
1387
  #: admin/views/placements-ad-label-position.php:13
1388
  #: admin/views/placements-ad-label.php:9
1389
  #: admin/views/placements-ad-label.php:11
1390
- #: admin/views/placements.php:269
1391
  #: modules/gadsense/includes/class-network-adsense.php:321
1392
  msgid "default"
1393
  msgstr ""
@@ -1517,7 +1519,7 @@ msgid "Minute"
1517
  msgstr ""
1518
 
1519
  #. translators: %1$s month, %2$s day, %3$s year, %4$s hour, %5$s minute.
1520
- #: admin/views/ad-submitbox-meta.php:38
1521
  msgctxt "order of expiry date fields 1: month, 2: day, 3: year, 4: hour, 5: minute"
1522
  msgid "%1$s %2$s, %3$s @ %4$s %5$s"
1523
  msgstr ""
@@ -1720,13 +1722,13 @@ msgstr ""
1720
 
1721
  #: admin/views/modal.php:36
1722
  #: admin/views/modal.php:37
1723
- #: admin/views/placements.php:320
1724
  #: admin/views/placements.php:321
 
1725
  msgid "Close"
1726
  msgstr ""
1727
 
1728
  #: admin/views/modal.php:39
1729
- #: admin/views/placements.php:231
1730
  msgid "Close and save"
1731
  msgstr ""
1732
 
@@ -1844,7 +1846,6 @@ msgstr ""
1844
  msgid "Adjust the placement options"
1845
  msgstr ""
1846
 
1847
- #. translators: %s is a URL.
1848
  #: admin/views/placement-injection-top.php:21
1849
  msgid "Ad not showing up? Take a look <a href=\"%s\" target=\"_blank\">here</a>"
1850
  msgstr ""
@@ -1938,12 +1939,10 @@ msgstr ""
1938
  msgid "Existing placement"
1939
  msgstr ""
1940
 
1941
- #. translators: %s is some HTML.
1942
  #: admin/views/placement-injection-top.php:168
1943
  msgid "Or use the shortcode %s to insert the ad into the content manually."
1944
  msgstr ""
1945
 
1946
- #. translators: %s is a URL.
1947
  #: admin/views/placement-injection-top.php:176
1948
  msgid "Learn more about your choices to display an ad in the <a href=\"%s\" target=\"_blank\">manual</a>."
1949
  msgstr ""
@@ -1997,12 +1996,12 @@ msgid "Placements updated"
1997
  msgstr ""
1998
 
1999
  #: admin/views/placements.php:25
2000
- #: admin/views/placements.php:465
2001
  msgid "Create a new placement"
2002
  msgstr ""
2003
 
2004
  #: admin/views/placements.php:26
2005
- #: admin/views/placements.php:467
2006
  msgid "New Placement"
2007
  msgstr ""
2008
 
@@ -2072,59 +2071,60 @@ msgstr ""
2072
  msgid "A minimum amount of words between automatically injected ads."
2073
  msgstr ""
2074
 
2075
- #: admin/views/placements.php:230
 
2076
  #: modules/import-export/views/page.php:26
2077
  msgid "Options"
2078
  msgstr ""
2079
 
2080
  #. Translators: %s is the name of a placement.
2081
- #: admin/views/placements.php:242
2082
  msgid "Placement type \"%s\" is missing and was reset to \"default\".<br/>Please check if the responsible add-on is activated."
2083
  msgstr ""
2084
 
2085
- #: admin/views/placements.php:313
2086
  msgid "template (PHP)"
2087
  msgstr ""
2088
 
2089
- #: admin/views/placements.php:348
2090
  msgid "Group"
2091
  msgstr ""
2092
 
2093
- #: admin/views/placements.php:364
2094
  msgid "after"
2095
  msgstr ""
2096
 
2097
- #: admin/views/placements.php:365
2098
  msgid "before"
2099
  msgstr ""
2100
 
2101
- #: admin/views/placements.php:378
2102
  msgid "position"
2103
  msgstr ""
2104
 
2105
- #: admin/views/placements.php:384
2106
  msgid "Important Notice"
2107
  msgstr ""
2108
 
2109
  #. Translators: %s is a URL.
2110
- #: admin/views/placements.php:407
2111
  msgid "Tutorial: <a href=\"%s\" target=\"_blank\">How to place visible ads in the header of your website</a>."
2112
  msgstr ""
2113
 
2114
- #: admin/views/placements.php:423
2115
  msgid "show all options"
2116
  msgstr ""
2117
 
2118
- #: admin/views/placements.php:446
2119
  msgid "edit conditions"
2120
  msgstr ""
2121
 
2122
- #: admin/views/placements.php:456
2123
  msgctxt "checkbox to remove placement"
2124
  msgid "delete"
2125
  msgstr ""
2126
 
2127
- #: admin/views/placements.php:463
2128
  msgid "Save Placements"
2129
  msgstr ""
2130
 
@@ -2540,6 +2540,10 @@ msgstr ""
2540
  msgid "Placement"
2541
  msgstr ""
2542
 
 
 
 
 
2543
  #. translators: %1$s is an anchor (link) opening tag, %2$s is the closing tag.
2544
  #: classes/ad-health-notices.php:865
2545
  #: modules/gadsense/includes/class-mapi.php:1644
@@ -2664,11 +2668,6 @@ msgctxt "for the \"custom\" content placement option"
2664
  msgid "custom"
2665
  msgstr ""
2666
 
2667
- #. translators: %s stands for the name of the "Disable level limitation" option and automatically translated as well
2668
- #: classes/ad_placements.php:1017
2669
- msgid "Set <em>%s</em> to show more ads"
2670
- msgstr ""
2671
-
2672
  #: classes/ad_type_content.php:35
2673
  msgid "Rich Content"
2674
  msgstr ""
@@ -3162,6 +3161,11 @@ msgstr ""
3162
  msgid "Closing the message"
3163
  msgstr ""
3164
 
 
 
 
 
 
3165
  #. translators: time zone name.
3166
  #: classes/utils.php:277
3167
  msgid "time of %s"
@@ -3465,64 +3469,75 @@ msgstr ""
3465
  msgid "Dummy Account Id"
3466
  msgstr ""
3467
 
3468
- #: modules/gadsense/admin/views/adsense-account.php:29
 
 
 
 
 
 
3469
  msgid "AdSense warnings"
3470
  msgstr ""
3471
 
3472
- #: modules/gadsense/admin/views/adsense-account.php:30
3473
- #: modules/gadsense/admin/views/adsense-account.php:64
3474
  msgid "dismiss"
3475
  msgstr ""
3476
 
3477
- #: modules/gadsense/admin/views/adsense-account.php:76
 
 
 
 
 
3478
  msgid "Revoke API acccess"
3479
  msgstr ""
3480
 
3481
- #: modules/gadsense/admin/views/adsense-account.php:82
3482
  msgid "Account holder name"
3483
  msgstr ""
3484
 
3485
- #: modules/gadsense/admin/views/adsense-account.php:85
3486
  #: modules/gadsense/includes/class-ad-type-adsense.php:91
3487
  msgid "The Publisher ID has an incorrect format. (must start with \"pub-\")"
3488
  msgstr ""
3489
 
3490
- #: modules/gadsense/admin/views/adsense-account.php:87
3491
  msgid "Your AdSense Publisher ID <em>(pub-xxxxxxxxxxxxxx)</em>"
3492
  msgstr ""
3493
 
3494
- #: modules/gadsense/admin/views/adsense-account.php:94
3495
  msgid "Yes, I have an AdSense account"
3496
  msgstr ""
3497
 
3498
- #: modules/gadsense/admin/views/adsense-account.php:96
3499
  msgid "Configure everything manually"
3500
  msgstr ""
3501
 
3502
- #: modules/gadsense/admin/views/adsense-account.php:99
3503
  msgid "No, I still don't have an AdSense account"
3504
  msgstr ""
3505
 
3506
- #: modules/gadsense/admin/views/adsense-account.php:100
3507
  msgid "Get a free AdSense account"
3508
  msgstr ""
3509
 
3510
  #. translators: %1$s is an opening a tag, %2$s is the closing one
3511
- #: modules/gadsense/admin/views/adsense-account.php:106
3512
- #: modules/gadsense/admin/views/adsense-account.php:197
3513
  msgid "See all %1$srecommended ad networks%2$s."
3514
  msgstr ""
3515
 
3516
- #: modules/gadsense/admin/views/adsense-account.php:171
3517
  msgid "How to choose specific positions for AdSense ad units"
3518
  msgstr ""
3519
 
3520
  #. translators: %1$s is the opening link tag to our manual; %2$s is the appropriate closing link tag; %3$s is the opening link tag to our help forum; %4$s is the appropriate closing link tag
3521
- #: modules/gadsense/admin/views/adsense-account.php:179
3522
  msgid "Problems with AdSense? Check out the %1$smanual%2$s or %3$sask here%4$s."
3523
  msgstr ""
3524
 
3525
- #: modules/gadsense/admin/views/adsense-account.php:211
3526
  #: modules/gadsense/admin/views/external-ads-links.php:38
3527
  msgid "Can not connect AdSense account. PHP version is too low."
3528
  msgstr ""
1
+ # Copyright (C) 2022 Thomas Maier, Advanced Ads GmbH
2
  # This file is distributed under the same license as the Advanced Ads plugin.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: Advanced Ads 1.31.0\n"
6
  "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/advanced-ads/\n"
7
  "Last-Translator: Thomas Maier <post@webzunft.de>\n"
8
  "Language-Team: webgilde <support@wpadvancedads.com>\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=UTF-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
12
+ "POT-Creation-Date: 2022-01-25T07:06:36-06:00\n"
13
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14
  "X-Generator: WP-CLI 2.4.0\n"
15
  "X-Domain: advanced-ads\n"
108
 
109
  #. translators: %s is the URL to add a new review, https://wordpress.org/support/plugin/advanced-ads/reviews/#new-post
110
  #. translators: %s is a URL.
111
+ #: admin/class-advanced-ads-admin.php:669
112
  #: admin/includes/class-overview-widgets.php:191
113
  msgid "Thank the developer with a &#9733;&#9733;&#9733;&#9733;&#9733; review on <a href=\"%s\" target=\"_blank\">wordpress.org</a>"
114
  msgstr ""
126
  #. translators: %s is a list of PHP extensions.
127
  #. Translators: %s is a name of a module.
128
  #: admin/includes/ad-health-notices.php:54
129
+ #: admin/views/placements.php:389
130
  msgid "Missing PHP extensions could cause issues. Please ask your hosting provider to enable them: %s"
131
  msgstr ""
132
 
228
  msgid "%s detected."
229
  msgstr ""
230
 
 
231
  #: admin/includes/ad-health-notices.php:253
232
  msgid "Learn how this might impact your ad setup."
233
  msgstr ""
311
  msgstr ""
312
 
313
  #: admin/includes/class-ad-groups-list.php:339
314
+ #: admin/views/placements.php:276
315
  #: modules/import-export/classes/import.php:153
316
  #: modules/import-export/classes/import.php:193
317
  #: modules/import-export/classes/import.php:595
320
  msgstr ""
321
 
322
  #: admin/includes/class-ad-groups-list.php:340
323
+ #: admin/views/placements.php:307
324
  msgid "Usage"
325
  msgstr ""
326
 
343
  msgid "No ad group created"
344
  msgstr ""
345
 
346
+ #: admin/includes/class-ad-type.php:265
347
+ #: admin/includes/class-ad-type.php:271
348
  msgid "Ad Details"
349
  msgstr ""
350
 
351
+ #: admin/includes/class-ad-type.php:266
352
+ #: admin/includes/class-ad-type.php:272
353
  msgid "Ad Planning"
354
  msgstr ""
355
 
356
+ #: admin/includes/class-ad-type.php:267
357
+ #: admin/includes/class-ad-type.php:273
358
  msgid "Ad Shortcode"
359
  msgstr ""
360
 
361
  #. translators: %s is the number of ads.
362
+ #: admin/includes/class-ad-type.php:437
363
  msgid "%s ad updated."
364
  msgid_plural "%s ads updated."
365
  msgstr[0] ""
366
  msgstr[1] ""
367
 
368
  #. translators: %s is the number of ads.
369
+ #: admin/includes/class-ad-type.php:439
370
  msgid "%s ad not updated, somebody is editing it."
371
  msgid_plural "%s ads not updated, somebody is editing them."
372
  msgstr[0] ""
373
  msgstr[1] ""
374
 
375
  #. translators: %s is the number of ads.
376
+ #: admin/includes/class-ad-type.php:441
377
  msgid "%s ad permanently deleted."
378
  msgid_plural "%s ads permanently deleted."
379
  msgstr[0] ""
380
  msgstr[1] ""
381
 
382
  #. translators: %s is the number of ads.
383
+ #: admin/includes/class-ad-type.php:443
384
  msgid "%s ad moved to the Trash."
385
  msgid_plural "%s ads moved to the Trash."
386
  msgstr[0] ""
387
  msgstr[1] ""
388
 
389
  #. translators: %s is the number of ads.
390
+ #: admin/includes/class-ad-type.php:445
391
  msgid "%s ad restored from the Trash."
392
  msgid_plural "%s ads restored from the Trash."
393
  msgstr[0] ""
394
  msgstr[1] ""
395
 
396
  #. Translators: %s is the time the ad was first saved.
397
+ #: admin/includes/class-ad-type.php:686
398
  msgid "Ad created on %s"
399
  msgstr ""
400
 
401
+ #: admin/includes/class-ad-type.php:878
402
+ #: admin/includes/class-ad-type.php:879
403
  msgid "Ad updated."
404
  msgstr ""
405
 
406
  #. translators: %s: date and time of the revision
407
+ #: admin/includes/class-ad-type.php:880
408
  msgid "Ad restored to revision from %s"
409
  msgstr ""
410
 
411
+ #: admin/includes/class-ad-type.php:881
412
+ #: admin/includes/class-ad-type.php:882
413
  msgid "Ad saved."
414
  msgstr ""
415
 
416
+ #: admin/includes/class-ad-type.php:883
417
  msgid "Ad submitted."
418
  msgstr ""
419
 
420
  #. translators: %1$s is a date.
421
+ #: admin/includes/class-ad-type.php:886
422
  msgid "Ad scheduled for: <strong>%1$s</strong>."
423
  msgstr ""
424
 
425
  #. translators: Publish box date format, see http://php.net/date.
426
+ #: admin/includes/class-ad-type.php:888
427
  msgid "M j, Y @ G:i"
428
  msgstr ""
429
 
430
+ #: admin/includes/class-ad-type.php:890
431
  msgid "Ad draft updated."
432
  msgstr ""
433
 
434
+ #: admin/includes/class-ad-type.php:946
435
  msgid "You don’t have access to ads. Please deactivate and re-enable Advanced Ads again to fix this."
436
  msgstr ""
437
 
438
+ #: admin/includes/class-ad-type.php:947
439
  #: classes/frontend_checks.php:503
440
  msgid "Get help"
441
  msgstr ""
479
  msgid "Please enter a valid license key"
480
  msgstr ""
481
 
482
+ #: admin/includes/class-licenses.php:170
483
+ #: admin/includes/class-licenses.php:360
484
+ msgid "The license status does not change as long as ADVANCED_ADS_SHOW_LICENSE_RESPONSE is enabled in wp-config.php."
485
+ msgstr ""
486
+
487
+ #: admin/includes/class-licenses.php:188
488
  msgid "License couldn’t be activated. Please try again later."
489
  msgstr ""
490
 
491
+ #: admin/includes/class-licenses.php:205
492
  msgid "This is the bundle license key."
493
  msgstr ""
494
 
495
+ #: admin/includes/class-licenses.php:206
496
  msgid "This is not the correct key for this add-on."
497
  msgstr ""
498
 
499
+ #: admin/includes/class-licenses.php:207
500
  msgid "There are no activations left."
501
  msgstr ""
502
 
503
  #. translators: %1$s is a starting link tag, %2$s is the closing one.
504
+ #: admin/includes/class-licenses.php:211
505
  msgid "You can manage activations in %1$syour account%2$s."
506
  msgstr ""
507
 
508
  #. translators: %1$s is a starting link tag, %2$s is the closing one.
509
+ #: admin/includes/class-licenses.php:217
510
  msgid "%1$sUpgrade%2$s for more activations."
511
  msgstr ""
512
 
513
  #. translators: %s is a string containing information about the issue.
514
+ #: admin/includes/class-licenses.php:231
515
  msgid "License is invalid. Reason: %s"
516
  msgstr ""
517
 
518
  #. translators: %s is a list of server information like IP address. Just keep it as is.
519
+ #: admin/includes/class-licenses.php:271
520
  msgid "Your request was blocked by our firewall. Please send us the following information to unblock you: %s."
521
  msgstr ""
522
 
523
+ #: admin/includes/class-licenses.php:333
524
  msgid "Error while trying to disable the license. Please contact support."
525
  msgstr ""
526
 
527
+ #: admin/includes/class-licenses.php:369
528
+ #: admin/includes/class-licenses.php:392
529
  msgid "License couldn’t be deactivated. Please try again later."
530
  msgstr ""
531
 
532
+ #: admin/includes/class-licenses.php:626
533
  msgid "Download failed. <a href=\"%s\">Click here to try another method</a>."
534
  msgstr ""
535
 
536
+ #: admin/includes/class-licenses.php:628
537
  msgid "Download failed. <a href=\"%s\" target=\"_blank\">Click here to learn why</a>."
538
  msgstr ""
539
 
540
+ #: admin/includes/class-list-filters.php:140
541
  #: modules/gadsense/admin/admin.php:73
542
  #: modules/gadsense/admin/views/adsense-ad-parameters.php:109
543
  msgid "Responsive"
544
  msgstr ""
545
 
546
+ #: admin/includes/class-list-filters.php:512
547
+ msgctxt "Post list header for expired ads."
548
+ msgid "Expired"
549
  msgstr ""
550
 
551
+ #: admin/includes/class-list-filters.php:552
552
+ msgctxt "Post list header for ads expiring in the future."
553
+ msgid "Expiring"
554
  msgstr ""
555
 
556
  #: admin/includes/class-menu.php:66
650
 
651
  #: admin/includes/class-meta-box.php:104
652
  #: admin/views/placements.php:184
653
+ #: admin/views/placements.php:430
654
  #: classes/ad-debug.php:152
655
  msgid "Display Conditions"
656
  msgstr ""
657
 
658
  #: admin/includes/class-meta-box.php:112
659
  #: admin/views/placements.php:193
660
+ #: admin/views/placements.php:438
661
  #: classes/ad-debug.php:239
662
  msgid "Visitor Conditions"
663
  msgstr ""
751
  msgstr ""
752
 
753
  #: admin/includes/class-overview-widgets.php:118
754
+ #: modules/gadsense/admin/views/adsense-account.php:87
755
+ #: modules/gadsense/admin/views/adsense-account.php:109
756
  msgid "Connect to AdSense"
757
  msgstr ""
758
 
963
  msgstr ""
964
 
965
  #: admin/includes/class-settings.php:178
966
+ #: classes/in-content-injector.php:615
967
  msgid "Disable level limitation"
968
  msgstr ""
969
 
1177
  msgstr ""
1178
 
1179
  #: admin/views/ad-group-list-ads.php:20
1180
+ #: admin/views/placements.php:348
1181
  #: classes/ad-debug.php:118
1182
  #: classes/ad-debug.php:167
1183
  #: classes/ad-debug.php:169
1204
 
1205
  #: admin/views/ad-group-list-form-row.php:56
1206
  #: admin/views/placements.php:64
1207
+ #: admin/views/placements.php:258
1208
  #: modules/gadsense/admin/views/adsense-ad-parameters.php:105
1209
  msgid "Type"
1210
  msgstr ""
1233
 
1234
  #: admin/views/ad-group-list-row.php:22
1235
  #: admin/views/ad-info.php:7
1236
+ #: admin/views/placements.php:311
1237
  msgid "shortcode"
1238
  msgstr ""
1239
 
1360
  msgstr ""
1361
 
1362
  #: admin/views/ad-list-filters.php:34
 
 
 
 
1363
  msgid "all ad groups"
1364
  msgstr ""
1365
 
1389
  #: admin/views/placements-ad-label-position.php:13
1390
  #: admin/views/placements-ad-label.php:9
1391
  #: admin/views/placements-ad-label.php:11
1392
+ #: admin/views/placements.php:270
1393
  #: modules/gadsense/includes/class-network-adsense.php:321
1394
  msgid "default"
1395
  msgstr ""
1519
  msgstr ""
1520
 
1521
  #. translators: %1$s month, %2$s day, %3$s year, %4$s hour, %5$s minute.
1522
+ #: admin/views/ad-submitbox-meta.php:37
1523
  msgctxt "order of expiry date fields 1: month, 2: day, 3: year, 4: hour, 5: minute"
1524
  msgid "%1$s %2$s, %3$s @ %4$s %5$s"
1525
  msgstr ""
1722
 
1723
  #: admin/views/modal.php:36
1724
  #: admin/views/modal.php:37
 
1725
  #: admin/views/placements.php:321
1726
+ #: admin/views/placements.php:322
1727
  msgid "Close"
1728
  msgstr ""
1729
 
1730
  #: admin/views/modal.php:39
1731
+ #: admin/views/placements.php:232
1732
  msgid "Close and save"
1733
  msgstr ""
1734
 
1846
  msgid "Adjust the placement options"
1847
  msgstr ""
1848
 
 
1849
  #: admin/views/placement-injection-top.php:21
1850
  msgid "Ad not showing up? Take a look <a href=\"%s\" target=\"_blank\">here</a>"
1851
  msgstr ""
1939
  msgid "Existing placement"
1940
  msgstr ""
1941
 
 
1942
  #: admin/views/placement-injection-top.php:168
1943
  msgid "Or use the shortcode %s to insert the ad into the content manually."
1944
  msgstr ""
1945
 
 
1946
  #: admin/views/placement-injection-top.php:176
1947
  msgid "Learn more about your choices to display an ad in the <a href=\"%s\" target=\"_blank\">manual</a>."
1948
  msgstr ""
1996
  msgstr ""
1997
 
1998
  #: admin/views/placements.php:25
1999
+ #: admin/views/placements.php:466
2000
  msgid "Create a new placement"
2001
  msgstr ""
2002
 
2003
  #: admin/views/placements.php:26
2004
+ #: admin/views/placements.php:468
2005
  msgid "New Placement"
2006
  msgstr ""
2007
 
2071
  msgid "A minimum amount of words between automatically injected ads."
2072
  msgstr ""
2073
 
2074
+ #. Translators: 1: "Options", 2: the name of a placement.
2075
+ #: admin/views/placements.php:231
2076
  #: modules/import-export/views/page.php:26
2077
  msgid "Options"
2078
  msgstr ""
2079
 
2080
  #. Translators: %s is the name of a placement.
2081
+ #: admin/views/placements.php:243
2082
  msgid "Placement type \"%s\" is missing and was reset to \"default\".<br/>Please check if the responsible add-on is activated."
2083
  msgstr ""
2084
 
2085
+ #: admin/views/placements.php:314
2086
  msgid "template (PHP)"
2087
  msgstr ""
2088
 
2089
+ #: admin/views/placements.php:349
2090
  msgid "Group"
2091
  msgstr ""
2092
 
2093
+ #: admin/views/placements.php:365
2094
  msgid "after"
2095
  msgstr ""
2096
 
2097
+ #: admin/views/placements.php:366
2098
  msgid "before"
2099
  msgstr ""
2100
 
2101
+ #: admin/views/placements.php:379
2102
  msgid "position"
2103
  msgstr ""
2104
 
2105
+ #: admin/views/placements.php:385
2106
  msgid "Important Notice"
2107
  msgstr ""
2108
 
2109
  #. Translators: %s is a URL.
2110
+ #: admin/views/placements.php:408
2111
  msgid "Tutorial: <a href=\"%s\" target=\"_blank\">How to place visible ads in the header of your website</a>."
2112
  msgstr ""
2113
 
2114
+ #: admin/views/placements.php:424
2115
  msgid "show all options"
2116
  msgstr ""
2117
 
2118
+ #: admin/views/placements.php:447
2119
  msgid "edit conditions"
2120
  msgstr ""
2121
 
2122
+ #: admin/views/placements.php:457
2123
  msgctxt "checkbox to remove placement"
2124
  msgid "delete"
2125
  msgstr ""
2126
 
2127
+ #: admin/views/placements.php:464
2128
  msgid "Save Placements"
2129
  msgstr ""
2130
 
2540
  msgid "Placement"
2541
  msgstr ""
2542
 
2543
+ #: classes/ad-expiration.php:84
2544
+ msgid "Expired"
2545
+ msgstr ""
2546
+
2547
  #. translators: %1$s is an anchor (link) opening tag, %2$s is the closing tag.
2548
  #: classes/ad-health-notices.php:865
2549
  #: modules/gadsense/includes/class-mapi.php:1644
2668
  msgid "custom"
2669
  msgstr ""
2670
 
 
 
 
 
 
2671
  #: classes/ad_type_content.php:35
2672
  msgid "Rich Content"
2673
  msgstr ""
3161
  msgid "Closing the message"
3162
  msgstr ""
3163
 
3164
+ #. translators: %s stands for the name of the "Disable level limitation" option and automatically translated as well
3165
+ #: classes/in-content-injector.php:614
3166
+ msgid "Set <em>%s</em> to show more ads"
3167
+ msgstr ""
3168
+
3169
  #. translators: time zone name.
3170
  #: classes/utils.php:277
3171
  msgid "time of %s"
3469
  msgid "Dummy Account Id"
3470
  msgstr ""
3471
 
3472
+ #. translators: 1: opening anchor tag for link to adsense account 2: closing anchor tag for link to adsense account
3473
+ #: modules/gadsense/admin/views/adsense-account.php:31
3474
+ msgid "Warning from your %1$sAdSense account%2$s"
3475
+ msgstr ""
3476
+
3477
+ #. translators: 1: opening anchor tag for link to adsense account 2: closing anchor tag for link to adsense account
3478
+ #: modules/gadsense/admin/views/adsense-account.php:31
3479
  msgid "AdSense warnings"
3480
  msgstr ""
3481
 
3482
+ #: modules/gadsense/admin/views/adsense-account.php:32
3483
+ #: modules/gadsense/admin/views/adsense-account.php:78
3484
  msgid "dismiss"
3485
  msgstr ""
3486
 
3487
+ #. translators: %s: date and time of last check in the format set in wp_options
3488
+ #: modules/gadsense/admin/views/adsense-account.php:64
3489
+ msgid "last checked: %s"
3490
+ msgstr ""
3491
+
3492
+ #: modules/gadsense/admin/views/adsense-account.php:90
3493
  msgid "Revoke API acccess"
3494
  msgstr ""
3495
 
3496
+ #: modules/gadsense/admin/views/adsense-account.php:96
3497
  msgid "Account holder name"
3498
  msgstr ""
3499
 
3500
+ #: modules/gadsense/admin/views/adsense-account.php:99
3501
  #: modules/gadsense/includes/class-ad-type-adsense.php:91
3502
  msgid "The Publisher ID has an incorrect format. (must start with \"pub-\")"
3503
  msgstr ""
3504
 
3505
+ #: modules/gadsense/admin/views/adsense-account.php:101
3506
  msgid "Your AdSense Publisher ID <em>(pub-xxxxxxxxxxxxxx)</em>"
3507
  msgstr ""
3508
 
3509
+ #: modules/gadsense/admin/views/adsense-account.php:108
3510
  msgid "Yes, I have an AdSense account"
3511
  msgstr ""
3512
 
3513
+ #: modules/gadsense/admin/views/adsense-account.php:110
3514
  msgid "Configure everything manually"
3515
  msgstr ""
3516
 
3517
+ #: modules/gadsense/admin/views/adsense-account.php:113
3518
  msgid "No, I still don't have an AdSense account"
3519
  msgstr ""
3520
 
3521
+ #: modules/gadsense/admin/views/adsense-account.php:114
3522
  msgid "Get a free AdSense account"
3523
  msgstr ""
3524
 
3525
  #. translators: %1$s is an opening a tag, %2$s is the closing one
3526
+ #: modules/gadsense/admin/views/adsense-account.php:120
3527
+ #: modules/gadsense/admin/views/adsense-account.php:211
3528
  msgid "See all %1$srecommended ad networks%2$s."
3529
  msgstr ""
3530
 
3531
+ #: modules/gadsense/admin/views/adsense-account.php:185
3532
  msgid "How to choose specific positions for AdSense ad units"
3533
  msgstr ""
3534
 
3535
  #. translators: %1$s is the opening link tag to our manual; %2$s is the appropriate closing link tag; %3$s is the opening link tag to our help forum; %4$s is the appropriate closing link tag
3536
+ #: modules/gadsense/admin/views/adsense-account.php:193
3537
  msgid "Problems with AdSense? Check out the %1$smanual%2$s or %3$sask here%4$s."
3538
  msgstr ""
3539
 
3540
+ #: modules/gadsense/admin/views/adsense-account.php:225
3541
  #: modules/gadsense/admin/views/external-ads-links.php:38
3542
  msgid "Can not connect AdSense account. PHP version is too low."
3543
  msgstr ""
lib/composer/autoload_classmap.php CHANGED
@@ -12,6 +12,7 @@ return array(
12
  'Advanced_Ads_Ad' => $baseDir . '/classes/ad.php',
13
  'Advanced_Ads_Ad_Ajax_Callbacks' => $baseDir . '/classes/ad_ajax_callbacks.php',
14
  'Advanced_Ads_Ad_Debug' => $baseDir . '/classes/ad-debug.php',
 
15
  'Advanced_Ads_Ad_Health_Notices' => $baseDir . '/classes/ad-health-notices.php',
16
  'Advanced_Ads_Ad_List_Filters' => $baseDir . '/admin/includes/class-list-filters.php',
17
  'Advanced_Ads_Ad_Network' => $baseDir . '/admin/includes/class-ad-network.php',
@@ -41,6 +42,7 @@ return array(
41
  'Advanced_Ads_Frontend_Notices' => $baseDir . '/classes/frontend-notices.php',
42
  'Advanced_Ads_Group' => $baseDir . '/classes/ad_group.php',
43
  'Advanced_Ads_Groups_List' => $baseDir . '/admin/includes/class-ad-groups-list.php',
 
44
  'Advanced_Ads_Inline_Css' => $baseDir . '/classes/inline-css.php',
45
  'Advanced_Ads_Model' => $baseDir . '/classes/ad-model.php',
46
  'Advanced_Ads_Overview_Widgets_Callbacks' => $baseDir . '/admin/includes/class-overview-widgets.php',
12
  'Advanced_Ads_Ad' => $baseDir . '/classes/ad.php',
13
  'Advanced_Ads_Ad_Ajax_Callbacks' => $baseDir . '/classes/ad_ajax_callbacks.php',
14
  'Advanced_Ads_Ad_Debug' => $baseDir . '/classes/ad-debug.php',
15
+ 'Advanced_Ads_Ad_Expiration' => $baseDir . '/classes/ad-expiration.php',
16
  'Advanced_Ads_Ad_Health_Notices' => $baseDir . '/classes/ad-health-notices.php',
17
  'Advanced_Ads_Ad_List_Filters' => $baseDir . '/admin/includes/class-list-filters.php',
18
  'Advanced_Ads_Ad_Network' => $baseDir . '/admin/includes/class-ad-network.php',
42
  'Advanced_Ads_Frontend_Notices' => $baseDir . '/classes/frontend-notices.php',
43
  'Advanced_Ads_Group' => $baseDir . '/classes/ad_group.php',
44
  'Advanced_Ads_Groups_List' => $baseDir . '/admin/includes/class-ad-groups-list.php',
45
+ 'Advanced_Ads_In_Content_Injector' => $baseDir . '/classes/in-content-injector.php',
46
  'Advanced_Ads_Inline_Css' => $baseDir . '/classes/inline-css.php',
47
  'Advanced_Ads_Model' => $baseDir . '/classes/ad-model.php',
48
  'Advanced_Ads_Overview_Widgets_Callbacks' => $baseDir . '/admin/includes/class-overview-widgets.php',
lib/composer/autoload_static.php CHANGED
@@ -13,6 +13,7 @@ class ComposerStaticInit_advanced_ads
13
  'Advanced_Ads_Ad' => __DIR__ . '/../..' . '/classes/ad.php',
14
  'Advanced_Ads_Ad_Ajax_Callbacks' => __DIR__ . '/../..' . '/classes/ad_ajax_callbacks.php',
15
  'Advanced_Ads_Ad_Debug' => __DIR__ . '/../..' . '/classes/ad-debug.php',
 
16
  'Advanced_Ads_Ad_Health_Notices' => __DIR__ . '/../..' . '/classes/ad-health-notices.php',
17
  'Advanced_Ads_Ad_List_Filters' => __DIR__ . '/../..' . '/admin/includes/class-list-filters.php',
18
  'Advanced_Ads_Ad_Network' => __DIR__ . '/../..' . '/admin/includes/class-ad-network.php',
@@ -42,6 +43,7 @@ class ComposerStaticInit_advanced_ads
42
  'Advanced_Ads_Frontend_Notices' => __DIR__ . '/../..' . '/classes/frontend-notices.php',
43
  'Advanced_Ads_Group' => __DIR__ . '/../..' . '/classes/ad_group.php',
44
  'Advanced_Ads_Groups_List' => __DIR__ . '/../..' . '/admin/includes/class-ad-groups-list.php',
 
45
  'Advanced_Ads_Inline_Css' => __DIR__ . '/../..' . '/classes/inline-css.php',
46
  'Advanced_Ads_Model' => __DIR__ . '/../..' . '/classes/ad-model.php',
47
  'Advanced_Ads_Overview_Widgets_Callbacks' => __DIR__ . '/../..' . '/admin/includes/class-overview-widgets.php',
13
  'Advanced_Ads_Ad' => __DIR__ . '/../..' . '/classes/ad.php',
14
  'Advanced_Ads_Ad_Ajax_Callbacks' => __DIR__ . '/../..' . '/classes/ad_ajax_callbacks.php',
15
  'Advanced_Ads_Ad_Debug' => __DIR__ . '/../..' . '/classes/ad-debug.php',
16
+ 'Advanced_Ads_Ad_Expiration' => __DIR__ . '/../..' . '/classes/ad-expiration.php',
17
  'Advanced_Ads_Ad_Health_Notices' => __DIR__ . '/../..' . '/classes/ad-health-notices.php',
18
  'Advanced_Ads_Ad_List_Filters' => __DIR__ . '/../..' . '/admin/includes/class-list-filters.php',
19
  'Advanced_Ads_Ad_Network' => __DIR__ . '/../..' . '/admin/includes/class-ad-network.php',
43
  'Advanced_Ads_Frontend_Notices' => __DIR__ . '/../..' . '/classes/frontend-notices.php',
44
  'Advanced_Ads_Group' => __DIR__ . '/../..' . '/classes/ad_group.php',
45
  'Advanced_Ads_Groups_List' => __DIR__ . '/../..' . '/admin/includes/class-ad-groups-list.php',
46
+ 'Advanced_Ads_In_Content_Injector' => __DIR__ . '/../..' . '/classes/in-content-injector.php',
47
  'Advanced_Ads_Inline_Css' => __DIR__ . '/../..' . '/classes/inline-css.php',
48
  'Advanced_Ads_Model' => __DIR__ . '/../..' . '/classes/ad-model.php',
49
  'Advanced_Ads_Overview_Widgets_Callbacks' => __DIR__ . '/../..' . '/admin/includes/class-overview-widgets.php',
modules/gadsense/admin/views/adsense-account.php CHANGED
@@ -26,18 +26,30 @@ if ( $has_token && isset( $mapi_options['accounts'][ $adsense_id ]['details'] )
26
  }
27
 
28
  $alerts = Advanced_Ads_AdSense_MAPI::get_stored_account_alerts( $adsense_id );
29
- $alerts_heading = __( 'AdSense warnings', 'advanced-ads' );
30
- $alerts_dismiss = __( 'dismiss', 'advanced-ads' );
31
 
 
 
 
32
  $connection_error_messages = Advanced_Ads_AdSense_MAPI::get_connect_error_messages();
33
-
34
- $alerts_advads_messages = Advanced_Ads_Adsense_MAPI::get_adsense_alert_messages();
35
 
36
  ?>
37
  <div id="mapi-account-alerts" data-heading="<?php echo esc_attr( $alerts_heading ); ?>" data-dismiss="<?php echo esc_attr( $alerts_dismiss ); ?>">
38
  <?php if ( is_array( $alerts ) && isset( $alerts['items'] ) && is_array( $alerts['items'] ) && $alerts['items'] ) : ?>
39
  <div class="card advads-notice-block advads-error">
40
- <h3><?php echo esc_html( $alerts_heading ); ?></h3>
 
 
 
 
 
 
 
 
 
 
 
 
41
  <ul>
42
  <?php foreach ( $alerts['items'] as $alert_id => $alert ) : ?>
43
  <?php $internal_id = isset( $alert['id'] ) ? $alert['id'] : str_replace( '-', '_', strtoupper( $alert['type'] ) ); ?>
@@ -48,6 +60,8 @@ $alerts_advads_messages = Advanced_Ads_Adsense_MAPI::get_adsense_alert_messages(
48
  <?php endif; ?>
49
  <?php endforeach; ?>
50
  </ul>
 
 
51
  </div>
52
  <?php endif; ?>
53
  </div>
26
  }
27
 
28
  $alerts = Advanced_Ads_AdSense_MAPI::get_stored_account_alerts( $adsense_id );
 
 
29
 
30
+ /* translators: 1: opening anchor tag for link to adsense account 2: closing anchor tag for link to adsense account */
31
+ $alerts_heading = $adsense_id ? sprintf( __( 'Warning from your %1$sAdSense account%2$s', 'advanced-ads' ), '<a target="_blank" href="https://www.google.com/adsense/new/u/1/' . $adsense_id . '/">', '</a>' ) : __( 'AdSense warnings', 'advanced-ads' );
32
+ $alerts_dismiss = __( 'dismiss', 'advanced-ads' );
33
  $connection_error_messages = Advanced_Ads_AdSense_MAPI::get_connect_error_messages();
34
+ $alerts_advads_messages = Advanced_Ads_Adsense_MAPI::get_adsense_alert_messages();
 
35
 
36
  ?>
37
  <div id="mapi-account-alerts" data-heading="<?php echo esc_attr( $alerts_heading ); ?>" data-dismiss="<?php echo esc_attr( $alerts_dismiss ); ?>">
38
  <?php if ( is_array( $alerts ) && isset( $alerts['items'] ) && is_array( $alerts['items'] ) && $alerts['items'] ) : ?>
39
  <div class="card advads-notice-block advads-error">
40
+ <h3>
41
+ <?php
42
+ echo wp_kses(
43
+ $alerts_heading,
44
+ array(
45
+ 'a' => array(
46
+ 'target' => true,
47
+ 'href' => true,
48
+ ),
49
+ )
50
+ );
51
+ ?>
52
+ </h3>
53
  <ul>
54
  <?php foreach ( $alerts['items'] as $alert_id => $alert ) : ?>
55
  <?php $internal_id = isset( $alert['id'] ) ? $alert['id'] : str_replace( '-', '_', strtoupper( $alert['type'] ) ); ?>
60
  <?php endif; ?>
61
  <?php endforeach; ?>
62
  </ul>
63
+ <?php /* translators: %s: date and time of last check in the format set in wp_options */ ?>
64
+ <p class="description alignright"><?php printf( __( 'last checked: %s', 'advanced-ads' ), $alerts['lastCheck'] ? esc_html( ( new DateTime( '@' . $alerts['lastCheck'], Advanced_Ads_Utils::get_wp_timezone() ) )->format( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) ) : '-' ); ?></p>
65
  </div>
66
  <?php endif; ?>
67
  </div>
readme.txt CHANGED
@@ -2,9 +2,9 @@
2
  Contributors: webzunft, advancedads
3
  Tags: ads, ad manager, ad rotation, adsense, banner
4
  Requires at least: 4.9
5
- Tested up to: 5.8
6
  Requires PHP: 5.6
7
- Stable tag: 1.30.5
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -322,6 +322,15 @@ Yes. You can use plenty of [hooks](https://wpadvancedads.com/codex/) to customiz
322
 
323
  == Changelog ==
324
 
 
 
 
 
 
 
 
 
 
325
  = 1.30.5 =
326
 
327
  - Fix: correct ad weight calculation if ad count in group is retained but ad ids change
@@ -339,6 +348,7 @@ Yes. You can use plenty of [hooks](https://wpadvancedads.com/codex/) to customiz
339
 
340
  = 1.30.2 =
341
 
 
342
  - Fix: prevent applying array functions to boolean in `Advanced_Ads_Group`
343
  - Fix: add default weight for ads added to groups via the ad edit screen
344
 
2
  Contributors: webzunft, advancedads
3
  Tags: ads, ad manager, ad rotation, adsense, banner
4
  Requires at least: 4.9
5
+ Tested up to: 5.9
6
  Requires PHP: 5.6
7
+ Stable tag: 1.31.0
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
322
 
323
  == Changelog ==
324
 
325
+ = 1.31.0 =
326
+
327
+ - Feature: add "Expired" and "Expiring" views to the ad overview list replacing the expiry date filter
328
+ - Improvement: use "saved" dashicon when an element was saved correctly or a process finished
329
+ - Improvement: don't report HTML5 tags or custom elements as invalid tags in custom ad content
330
+ - Improvement: optimize warnings from AdSense account and clarify that these warnings are not from Advanced Ads
331
+ - Improvement: separate `inject_in_content` code into class `Advanced_Ads_In_Content_Injector`
332
+ - Improvement: update video manual display conditions
333
+
334
  = 1.30.5 =
335
 
336
  - Fix: correct ad weight calculation if ad count in group is retained but ad ids change
348
 
349
  = 1.30.2 =
350
 
351
+ - Improvement: hide feedback form when the plugin is disabled multiple times without feedback
352
  - Fix: prevent applying array functions to boolean in `Advanced_Ads_Group`
353
  - Fix: add default weight for ads added to groups via the ad edit screen
354