Advanced Ads - Version 1.17.3

Version Description

  • prevented content injection into specific elements where ads cause issues
  • assign advads-stop-injection class to any element into which you dont want to automatically inject ads
  • fixed possible cURL error when checking existing ads.txt file locally
Download this release

Release Info

Developer webzunft
Plugin Icon 128x128 Advanced Ads
Version 1.17.3
Comparing to
See all releases

Code changes from version 1.17.2 to 1.17.3

admin/assets/css/admin.css CHANGED
@@ -275,7 +275,7 @@ fieldset.advads-group-add-ad { margin-top: 1em; }
275
  .advads-placements-new-form .advads-placement-type label .description { padding: 10px; }
276
  .advads-placements-new-form .advads-error-message { display: none; }
277
  .advads-placements-table { min-width: 80%; border-collapse: collapse; background: #fff; }
278
- .advads-placements-table tbody tr { border-top: 1px solid #ddd; }
279
  .advads-placements-table tbody tr td:first-child { width: 70px; }
280
  .advads-placements-table tbody tr td:nth-child(2) { width: 140px; }
281
  .advads-placements-table tbody tr td:nth-child(2) { font-size: 1.2em; }
@@ -364,7 +364,7 @@ tr:hover .on-hover { display: block; }
364
  }
365
 
366
  /**
367
- * Connect to Google AdSense modal
368
  */
369
 
370
  .gadsense-modal-error {
@@ -521,8 +521,8 @@ div.advads-stats-box div.advads-stats-box-main{
521
  font-size: 18px;
522
  }
523
  div.advads-stats-dd-container {
524
- position:relative;
525
- float:right;
526
  text-align:right;
527
  }
528
  div.advads-stats-dd-button{
275
  .advads-placements-new-form .advads-placement-type label .description { padding: 10px; }
276
  .advads-placements-new-form .advads-error-message { display: none; }
277
  .advads-placements-table { min-width: 80%; border-collapse: collapse; background: #fff; }
278
+ .advads-placements-table > tbody > tr { border-top: 1px solid #ddd; }
279
  .advads-placements-table tbody tr td:first-child { width: 70px; }
280
  .advads-placements-table tbody tr td:nth-child(2) { width: 140px; }
281
  .advads-placements-table tbody tr td:nth-child(2) { font-size: 1.2em; }
364
  }
365
 
366
  /**
367
+ * Connect to Google AdSense modal
368
  */
369
 
370
  .gadsense-modal-error {
521
  font-size: 18px;
522
  }
523
  div.advads-stats-dd-container {
524
+ position:relative;
525
+ float:right;
526
  text-align:right;
527
  }
528
  div.advads-stats-dd-button{
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.17.2
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.17.2' );
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.17.3
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.17.3' );
43
 
44
  // Autoloading, modules and functions.
45
 
classes/ad-ajax.php CHANGED
@@ -7,8 +7,10 @@
7
  */
8
  class Advanced_Ads_Ajax {
9
 
10
- private function __construct()
11
- {
 
 
12
  add_action( 'wp_ajax_advads_ad_select', array( $this, 'advads_ajax_ad_select' ) );
13
  add_action( 'wp_ajax_nopriv_advads_ad_select', array( $this, 'advads_ajax_ad_select' ) );
14
  add_action( 'wp_ajax_advads-ad-health-notice-push', array( $this, 'ad_health_notice_push' ) );
@@ -16,12 +18,21 @@ class Advanced_Ads_Ajax {
16
  add_action( 'wp_ajax_advads-ad-frontend-notice-update', array( $this, 'frontend_notice_update' ) );
17
  }
18
 
 
 
 
 
 
19
  private static $instance;
20
 
21
- public static function get_instance()
22
- {
23
- if ( ! isset(self::$instance) ) {
24
- self::$instance = new self;
 
 
 
 
25
  }
26
 
27
  return self::$instance;
@@ -31,65 +42,71 @@ class Advanced_Ads_Ajax {
31
  * Simple wp ajax interface for ad selection.
32
  */
33
  public function advads_ajax_ad_select() {
34
- // set proper header
35
  header( 'Content-Type: application/json; charset: utf-8' );
36
 
37
- // allow modules / add ons to test (this is rather late but should happen before anything important is called)
38
  do_action( 'advanced-ads-ajax-ad-select-init' );
39
 
40
- $adIds = isset( $_REQUEST['ad_ids'] ) ? $_REQUEST['ad_ids'] : null;
41
- if ( is_string( $adIds ) ) {
42
- $adIds = json_decode( $adIds, true );
43
  }
44
- if (is_array($adIds)) { // ads loaded previously and passed by query
45
- Advanced_Ads::get_instance()->current_ads += $adIds;
46
  }
47
 
48
- $deferedAds = isset( $_REQUEST['deferedAds'] ) ? $_REQUEST['deferedAds'] : null;
49
- if ( $deferedAds ) { // load all ajax ads with a single request
50
  $response = array();
51
 
52
  $requests_by_blog = array();
53
- foreach ( (array) $deferedAds as $request ) {
54
- $blog_id = isset( $request['blog_id'] ) ? $request['blog_id'] : get_current_blog_id();
55
  $requests_by_blog[ $blog_id ][] = $request;
56
  }
57
  foreach ( $requests_by_blog as $blog_id => $requests ) {
58
- if ( $blog_id !== get_current_blog_id() ) { Advanced_Ads::get_instance()->switch_to_blog( $blog_id ); }
 
 
59
 
60
  foreach ( $requests as $request ) {
61
- $result = $this->select_one( $request );
62
  $result['elementId'] = ! empty( $request['elementId'] ) ? $request['elementId'] : null;
63
- $response[] = $result;
64
  }
65
 
66
- if ( $blog_id !== get_current_blog_id() ) { Advanced_Ads::get_instance()->restore_current_blog(); }
 
 
67
  }
68
 
69
- echo json_encode( $response );
70
  die();
71
  }
72
 
73
  $response = $this->select_one( $_REQUEST );
74
- echo json_encode( $response );
75
  die();
76
  }
77
 
78
  /**
79
  * Provides a single ad (ad, group, placement) given ID and selection method.
80
  *
81
- * @param $request array
 
 
82
  */
83
  private function select_one( $request ) {
84
- // init handlers
85
- $selector = Advanced_Ads_Select::get_instance();
86
- $methods = $selector->get_methods();
87
- $method = isset( $request['ad_method'] ) ? (string) $request['ad_method'] : null;
88
- $id = isset( $request['ad_id'] ) ? (string) $request['ad_id'] : null;
89
  $arguments = isset( $request['ad_args'] ) ? $request['ad_args'] : array();
90
- if (is_string($arguments)) {
91
- $arguments = stripslashes($arguments);
92
- $arguments = json_decode($arguments, true);
93
  }
94
  if ( ! empty( $request['elementId'] ) ) {
95
  $arguments['cache_busting_elementid'] = $request['elementId'];
@@ -98,41 +115,60 @@ class Advanced_Ads_Ajax {
98
  $response = array();
99
  if ( isset( $methods[ $method ] ) && isset( $id ) ) {
100
  $advads = Advanced_Ads::get_instance();
101
- $l = count( $advads->current_ads );
102
 
103
- // build content
104
  $content = $selector->get_ad_by_method( $id, $method, $arguments );
105
- $adIds = array_slice( $advads->current_ads, $l ); // ads loaded by this request
106
-
107
- $r = array( 'status' => 'success', 'item' => $content, 'id' => $id, 'method' => $method, 'ads' => $adIds, 'blog_id' => get_current_blog_id() );
108
- return apply_filters( 'advanced-ads-cache-busting-item', $r, array( 'id' => $id, 'method' => $method, 'args' => $arguments ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  } else {
110
- // report error
111
- return array( 'status' => 'error', 'message' => 'No valid ID or METHOD found.' );
 
 
 
112
  }
113
  }
114
 
115
  /**
116
  * Push an Ad Health notice to the queue in the backend
117
  */
118
- public function ad_health_notice_push(){
119
-
120
  check_ajax_referer( 'advanced-ads-ad-health-ajax-nonce', 'nonce' );
121
-
122
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads') ) ) {
123
  return;
124
  }
125
-
126
- $key = ( !empty( $_REQUEST['key'] ) ) ? esc_attr( $_REQUEST['key'] ) : false;
127
- $attr = ( !empty( $_REQUEST['attr'] ) && is_array( $_REQUEST['attr'] ) ) ? $_REQUEST['attr'] : array();
128
-
129
  // update or new entry?
130
- if( isset( $attr['mode'] ) && 'update' === $attr['mode'] ){
131
  Advanced_Ads_Ad_Health_Notices::get_instance()->update( $key, $attr );
132
  } else {
133
  Advanced_Ads_Ad_Health_Notices::get_instance()->add( $key, $attr );
134
- }
135
-
136
  die();
137
  }
138
 
@@ -147,11 +183,12 @@ class Advanced_Ads_Ajax {
147
  return;
148
  }
149
 
150
- $key = ( !empty( $_REQUEST['key'] ) ) ? esc_attr( $_REQUEST['key'] ) : false;
151
- $attr = ( !empty( $_REQUEST['attr'] ) && is_array( $_REQUEST['attr'] ) ) ? $_REQUEST['attr'] : array();
152
 
153
  // update or new entry?
154
- if( isset( $attr['mode'] ) && 'update' === $attr['mode'] ){
 
155
  // Advanced_Ads_Frontend_Notices::get_instance()->update( $key, $attr );
156
  } else {
157
  Advanced_Ads_Frontend_Notices::get_instance()->update( $key, $attr );
7
  */
8
  class Advanced_Ads_Ajax {
9
 
10
+ /**
11
+ * Advanced_Ads_Ajax constructor.
12
+ */
13
+ private function __construct() {
14
  add_action( 'wp_ajax_advads_ad_select', array( $this, 'advads_ajax_ad_select' ) );
15
  add_action( 'wp_ajax_nopriv_advads_ad_select', array( $this, 'advads_ajax_ad_select' ) );
16
  add_action( 'wp_ajax_advads-ad-health-notice-push', array( $this, 'ad_health_notice_push' ) );
18
  add_action( 'wp_ajax_advads-ad-frontend-notice-update', array( $this, 'frontend_notice_update' ) );
19
  }
20
 
21
+ /**
22
+ * Instance of Advanced_Ads_Ajax
23
+ *
24
+ * @var $instance
25
+ */
26
  private static $instance;
27
 
28
+ /**
29
+ * Instance getter
30
+ *
31
+ * @return Advanced_Ads_Ajax
32
+ */
33
+ public static function get_instance() {
34
+ if ( ! isset( self::$instance ) ) {
35
+ self::$instance = new self();
36
  }
37
 
38
  return self::$instance;
42
  * Simple wp ajax interface for ad selection.
43
  */
44
  public function advads_ajax_ad_select() {
45
+ // set proper header.
46
  header( 'Content-Type: application/json; charset: utf-8' );
47
 
48
+ // allow modules / add ons to test (this is rather late but should happen before anything important is called).
49
  do_action( 'advanced-ads-ajax-ad-select-init' );
50
 
51
+ $ad_ids = isset( $_REQUEST['ad_ids'] ) ? $_REQUEST['ad_ids'] : null;
52
+ if ( is_string( $ad_ids ) ) {
53
+ $ad_ids = json_decode( $ad_ids, true );
54
  }
55
+ if ( is_array( $ad_ids ) ) { // ads loaded previously and passed by query.
56
+ Advanced_Ads::get_instance()->current_ads += $ad_ids;
57
  }
58
 
59
+ $defered_ads = isset( $_REQUEST['deferedAds'] ) ? $_REQUEST['deferedAds'] : null;
60
+ if ( $defered_ads ) { // load all ajax ads with a single request.
61
  $response = array();
62
 
63
  $requests_by_blog = array();
64
+ foreach ( (array) $defered_ads as $request ) {
65
+ $blog_id = isset( $request['blog_id'] ) ? $request['blog_id'] : get_current_blog_id();
66
  $requests_by_blog[ $blog_id ][] = $request;
67
  }
68
  foreach ( $requests_by_blog as $blog_id => $requests ) {
69
+ if ( get_current_blog_id() !== $blog_id ) {
70
+ Advanced_Ads::get_instance()->switch_to_blog( $blog_id );
71
+ }
72
 
73
  foreach ( $requests as $request ) {
74
+ $result = $this->select_one( $request );
75
  $result['elementId'] = ! empty( $request['elementId'] ) ? $request['elementId'] : null;
76
+ $response[] = $result;
77
  }
78
 
79
+ if ( get_current_blog_id() !== $blog_id ) {
80
+ Advanced_Ads::get_instance()->restore_current_blog();
81
+ }
82
  }
83
 
84
+ echo wp_json_encode( $response );
85
  die();
86
  }
87
 
88
  $response = $this->select_one( $_REQUEST );
89
+ echo wp_json_encode( $response );
90
  die();
91
  }
92
 
93
  /**
94
  * Provides a single ad (ad, group, placement) given ID and selection method.
95
  *
96
+ * @param array $request request.
97
+ *
98
+ * @return array
99
  */
100
  private function select_one( $request ) {
101
+ // init handlers.
102
+ $selector = Advanced_Ads_Select::get_instance();
103
+ $methods = $selector->get_methods();
104
+ $method = isset( $request['ad_method'] ) ? (string) $request['ad_method'] : null;
105
+ $id = isset( $request['ad_id'] ) ? (string) $request['ad_id'] : null;
106
  $arguments = isset( $request['ad_args'] ) ? $request['ad_args'] : array();
107
+ if ( is_string( $arguments ) ) {
108
+ $arguments = stripslashes( $arguments );
109
+ $arguments = json_decode( $arguments, true );
110
  }
111
  if ( ! empty( $request['elementId'] ) ) {
112
  $arguments['cache_busting_elementid'] = $request['elementId'];
115
  $response = array();
116
  if ( isset( $methods[ $method ] ) && isset( $id ) ) {
117
  $advads = Advanced_Ads::get_instance();
118
+ $l = count( $advads->current_ads );
119
 
120
+ // build content.
121
  $content = $selector->get_ad_by_method( $id, $method, $arguments );
122
+ $ad_ids = array_slice( $advads->current_ads, $l ); // ads loaded by this request.
123
+
124
+ $r = array(
125
+ 'status' => 'success',
126
+ 'item' => $content,
127
+ 'id' => $id,
128
+ 'method' => $method,
129
+ 'ads' => $ad_ids,
130
+ 'blog_id' => get_current_blog_id(),
131
+ );
132
+
133
+ return apply_filters(
134
+ 'advanced-ads-cache-busting-item',
135
+ $r,
136
+ array(
137
+ 'id' => $id,
138
+ 'method' => $method,
139
+ 'args' => $arguments,
140
+ )
141
+ );
142
  } else {
143
+ // report error.
144
+ return array(
145
+ 'status' => 'error',
146
+ 'message' => 'No valid ID or METHOD found.',
147
+ );
148
  }
149
  }
150
 
151
  /**
152
  * Push an Ad Health notice to the queue in the backend
153
  */
154
+ public function ad_health_notice_push() {
155
+
156
  check_ajax_referer( 'advanced-ads-ad-health-ajax-nonce', 'nonce' );
157
+
158
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads' ) ) ) {
159
  return;
160
  }
161
+
162
+ $key = ( ! empty( $_REQUEST['key'] ) ) ? esc_attr( $_REQUEST['key'] ) : false;
163
+ $attr = ( ! empty( $_REQUEST['attr'] ) && is_array( $_REQUEST['attr'] ) ) ? $_REQUEST['attr'] : array();
164
+
165
  // update or new entry?
166
+ if ( isset( $attr['mode'] ) && 'update' === $attr['mode'] ) {
167
  Advanced_Ads_Ad_Health_Notices::get_instance()->update( $key, $attr );
168
  } else {
169
  Advanced_Ads_Ad_Health_Notices::get_instance()->add( $key, $attr );
170
+ }
171
+
172
  die();
173
  }
174
 
183
  return;
184
  }
185
 
186
+ $key = ( ! empty( $_REQUEST['key'] ) ) ? esc_attr( $_REQUEST['key'] ) : false;
187
+ $attr = ( ! empty( $_REQUEST['attr'] ) && is_array( $_REQUEST['attr'] ) ) ? $_REQUEST['attr'] : array();
188
 
189
  // update or new entry?
190
+ if ( isset( $attr['mode'] ) && 'update' === $attr['mode'] ) {
191
+ die();
192
  // Advanced_Ads_Frontend_Notices::get_instance()->update( $key, $attr );
193
  } else {
194
  Advanced_Ads_Frontend_Notices::get_instance()->update( $key, $attr );
classes/ad-health-notices.php CHANGED
@@ -172,7 +172,7 @@ class Advanced_Ads_Ad_Health_Notices {
172
  if ( isset( $_notice['closed'] ) ) {
173
  // remove notice when timeout expired – was closed longer ago than timeout set in the notice options.
174
  if ( empty( $notice_array['timeout'] )
175
- || ( ( $time - $_notice['closed'] ) > $notice_array['timeout'] ) ) {
176
  $this->remove( $_key );
177
  } else {
178
  // just ignore notice if timeout is still valid.
@@ -194,7 +194,6 @@ class Advanced_Ads_Ad_Health_Notices {
194
  add_filter( 'removable_query_args', array( $this, 'remove_query_vars_after_notice_update' ) );
195
  }
196
 
197
-
198
  // load hidden notices.
199
  $this->ignore = $this->get_valid_ignored();
200
 
@@ -237,7 +236,7 @@ class Advanced_Ads_Ad_Health_Notices {
237
 
238
  // run only daily unless we are on an Advanced Ads related page.
239
  if ( ! Advanced_Ads_Admin::screen_belongs_to_advanced_ads()
240
- && get_transient( self::DAILY_CHECK_TRANSIENT_NAME ) ) {
241
  return;
242
  }
243
 
@@ -325,12 +324,12 @@ class Advanced_Ads_Ad_Health_Notices {
325
  * Add a notice to the queue
326
  *
327
  * @param string $notice_key notice key to be added to the notice array.
328
- * @param array $atts additional attributes.
329
  *
330
- * attributes
331
- * - append_key string attached to the key; enables to create multiple messages for one original key
332
- * - append_text text added to the default message
333
- * - ad_id ID of an ad, attaches the link to the ad edit page to the message
334
  */
335
  public function add( $notice_key, $atts = array() ) {
336
 
@@ -342,10 +341,10 @@ class Advanced_Ads_Ad_Health_Notices {
342
  // add string to key.
343
  if ( ! empty( $atts['append_key'] ) ) {
344
  $orig_notice_key = $notice_key;
345
- $notice_key .= $atts['append_key'];
346
  }
347
 
348
- $notice_key = esc_attr( $notice_key );
349
  $options_before = $options = $this->options();
350
 
351
  // load notices from "queue".
@@ -403,10 +402,10 @@ class Advanced_Ads_Ad_Health_Notices {
403
  * Updating an existing notice or add it, if it doesn’t exist, yet
404
  *
405
  * @param string $notice_key notice key to be added to the notice array.
406
- * @param array $atts additional attributes.
407
  *
408
- * attributes:
409
- * - append_text – text added to the default message
410
  */
411
  public function update( $notice_key, $atts = array() ) {
412
 
@@ -416,8 +415,9 @@ class Advanced_Ads_Ad_Health_Notices {
416
  }
417
 
418
  // check if the notice already exists.
419
- $notice_key = esc_attr( $notice_key );
420
- $options_before = $options = $this->options();
 
421
 
422
  // load notices from "queue".
423
  $notices = isset( $options['notices'] ) ? $options['notices'] : array();
@@ -500,9 +500,10 @@ class Advanced_Ads_Ad_Health_Notices {
500
  }
501
 
502
  // get notices from options.
503
- $options_before = $options = $this->options();
 
504
  if ( ! isset( $options['notices'] )
505
- || ! is_array( $options['notices'] )
506
  || ! isset( $options['notices'][ $notice_key ] ) ) {
507
  return;
508
  }
@@ -533,7 +534,8 @@ class Advanced_Ads_Ad_Health_Notices {
533
  }
534
 
535
  // get options.
536
- $options_before = $options = $this->options();
 
537
  $ignored = isset( $options['ignore'] ) && is_array( $options['ignore'] ) ? $options['ignore'] : array();
538
 
539
  // adds notice key to ignore array if it doesn’t exist already.
@@ -559,7 +561,8 @@ class Advanced_Ads_Ad_Health_Notices {
559
  public function unignore() {
560
 
561
  // get options.
562
- $options_before = $options = $this->options();
 
563
 
564
  // empty ignore value.
565
  $options['ignore'] = array();
@@ -592,64 +595,66 @@ class Advanced_Ads_Ad_Health_Notices {
592
  * Display notices in a list
593
  *
594
  * @param string $type which type of notice to show; default: 'problem'.
595
- *
596
  */
597
  public function display( $type = 'problem' ) {
598
 
599
  // iterate through notices.
600
  ?>
601
- <ul class="advads-ad-health-notices advads-ad-health-notices-<?php echo $type; ?>"><?php
602
-
603
- // failsafe in case this is not an array.
604
- if ( ! is_array( $this->notices ) ) {
605
- return;
606
- }
607
-
608
- foreach ( $this->notices as $_notice_key => $_notice ) {
609
-
610
- $notice_array = $this->get_notice_array_for_key( $_notice_key );
611
-
612
- // remove the notice if key doesn’t exist anymore.
613
- if ( array() === $notice_array ) {
614
- $this->remove( $_notice_key );
615
- }
616
-
617
- $notice_type = isset( $notice_array['type'] ) ? $notice_array['type'] : 'problem';
618
-
619
- // skip if type is not correct.
620
- if ( $notice_type !== $type ) {
621
- continue;
622
- }
623
-
624
- if ( ! empty( $_notice['text'] ) ) {
625
- $text = $_notice['text'];
626
- } elseif ( isset( $notice_array['text'] ) ) {
627
- $text = $notice_array['text'];
628
- } else {
629
- continue;
630
- }
631
-
632
- // attach "append_text".
633
- if ( ! empty( $_notice['append_text'] ) ) {
634
- $text .= $_notice['append_text'];
635
- }
636
-
637
- // attach "get help" link.
638
- if ( ! empty( $_notice['get_help_link'] ) ) {
639
- $text .= $this->get_help_link( $_notice['get_help_link'] );
640
- } elseif ( isset( $notice_array['get_help_link'] ) ) {
641
- $text .= $this->get_help_link( $notice_array['get_help_link'] );
642
- }
643
-
644
- $can_hide = ( ! isset( $notice_array['can_hide'] ) || true === $notice_array['can_hide'] ) ? true : false;
645
- $hide = ( ! isset( $notice_array['hide'] ) || true === $notice_array['hide'] ) ? true : false;
646
- $is_hidden = in_array( $_notice_key, $this->ignore, true ) ? true : false;
647
- $date = isset( $_notice['time'] ) ? date_i18n( get_option( 'date_format' ), $_notice['time'] ) : false;
648
-
649
- include ADVADS_BASE_PATH . '/admin/views/overview-notice-row.php';
650
- }
651
-
652
- ?></ul><?php
 
 
 
653
  }
654
 
655
  /**
@@ -765,7 +770,7 @@ class Advanced_Ads_Ad_Health_Notices {
765
  $notice_array = $this->get_notice_array_for_key( $_key );
766
 
767
  if ( isset( $notice_array['type'] ) && $type === $notice_array['type']
768
- && ( ! isset( $this->ignore ) || false === array_search( $_key, $this->ignore, true ) ) ) {
769
  $notices_by_type[ $_key ] = $_notice;
770
  }
771
  }
@@ -824,18 +829,24 @@ class Advanced_Ads_Ad_Health_Notices {
824
  * Add notification when an ad expires based on the expiry date
825
  *
826
  * @param integer $ad_id ID of the ad.
827
- * @param object $ad ad object.
828
  */
829
  public function ad_expired( $ad_id, $ad ) {
830
  $id = ! empty( $ad_id ) ? absint( $ad_id ) : 0;
831
- $this->update( 'ad_expired', array( 'append_key' => $id, 'ad_id' => $id ) );
 
 
 
 
 
 
832
  }
833
 
834
  /**
835
  * Get AdSense error link
836
  * this is a copy of Advanced_Ads_AdSense_MAPI::get_adsense_error_link() which might not be available all the time
837
  *
838
- * @param string $code error code
839
  *
840
  * @return string link
841
  */
@@ -849,14 +860,12 @@ class Advanced_Ads_Ad_Health_Notices {
849
  }
850
 
851
  // is a copy of Advanced_Ads_AdSense_MAPI::get_adsense_error_link().
852
- $link = sprintf(
853
  // translators: %1$s is an anchor (link) opening tag, %2$s is the closing tag.
854
  esc_attr__( 'Learn more about AdSense account issues %1$shere%2$s.', 'advanced-ads' ),
855
  '<a href="' . ADVADS_URL . 'adsense-errors/#utm_source=advanced-ads&utm_medium=link&utm_campaign=adsense-error' . $code . '" target="_blank">',
856
  '</a>'
857
  );
858
-
859
- return $link;
860
  }
861
 
862
  /**
172
  if ( isset( $_notice['closed'] ) ) {
173
  // remove notice when timeout expired – was closed longer ago than timeout set in the notice options.
174
  if ( empty( $notice_array['timeout'] )
175
+ || ( ( $time - $_notice['closed'] ) > $notice_array['timeout'] ) ) {
176
  $this->remove( $_key );
177
  } else {
178
  // just ignore notice if timeout is still valid.
194
  add_filter( 'removable_query_args', array( $this, 'remove_query_vars_after_notice_update' ) );
195
  }
196
 
 
197
  // load hidden notices.
198
  $this->ignore = $this->get_valid_ignored();
199
 
236
 
237
  // run only daily unless we are on an Advanced Ads related page.
238
  if ( ! Advanced_Ads_Admin::screen_belongs_to_advanced_ads()
239
+ && get_transient( self::DAILY_CHECK_TRANSIENT_NAME ) ) {
240
  return;
241
  }
242
 
324
  * Add a notice to the queue
325
  *
326
  * @param string $notice_key notice key to be added to the notice array.
327
+ * @param array $atts additional attributes.
328
  *
329
+ * attributes
330
+ * - append_key string attached to the key; enables to create multiple messages for one original key
331
+ * - append_text text added to the default message
332
+ * - ad_id ID of an ad, attaches the link to the ad edit page to the message
333
  */
334
  public function add( $notice_key, $atts = array() ) {
335
 
341
  // add string to key.
342
  if ( ! empty( $atts['append_key'] ) ) {
343
  $orig_notice_key = $notice_key;
344
+ $notice_key .= $atts['append_key'];
345
  }
346
 
347
+ $notice_key = esc_attr( $notice_key );
348
  $options_before = $options = $this->options();
349
 
350
  // load notices from "queue".
402
  * Updating an existing notice or add it, if it doesn’t exist, yet
403
  *
404
  * @param string $notice_key notice key to be added to the notice array.
405
+ * @param array $atts additional attributes.
406
  *
407
+ * attributes:
408
+ * - append_text – text added to the default message
409
  */
410
  public function update( $notice_key, $atts = array() ) {
411
 
415
  }
416
 
417
  // check if the notice already exists.
418
+ $notice_key = esc_attr( $notice_key );
419
+ $options = $this->options();
420
+ $options_before = $options;
421
 
422
  // load notices from "queue".
423
  $notices = isset( $options['notices'] ) ? $options['notices'] : array();
500
  }
501
 
502
  // get notices from options.
503
+ $options = $this->options();
504
+ $options_before = $options;
505
  if ( ! isset( $options['notices'] )
506
+ || ! is_array( $options['notices'] )
507
  || ! isset( $options['notices'][ $notice_key ] ) ) {
508
  return;
509
  }
534
  }
535
 
536
  // get options.
537
+ $options = $this->options();
538
+ $options_before = $options;
539
  $ignored = isset( $options['ignore'] ) && is_array( $options['ignore'] ) ? $options['ignore'] : array();
540
 
541
  // adds notice key to ignore array if it doesn’t exist already.
561
  public function unignore() {
562
 
563
  // get options.
564
+ $options = $this->options();
565
+ $options_before = $options;
566
 
567
  // empty ignore value.
568
  $options['ignore'] = array();
595
  * Display notices in a list
596
  *
597
  * @param string $type which type of notice to show; default: 'problem'.
 
598
  */
599
  public function display( $type = 'problem' ) {
600
 
601
  // iterate through notices.
602
  ?>
603
+ <ul class="advads-ad-health-notices advads-ad-health-notices-<?php echo esc_attr( $type ); ?>">
604
+ <?php
605
+
606
+ // failsafe in case this is not an array.
607
+ if ( ! is_array( $this->notices ) ) {
608
+ return;
609
+ }
610
+
611
+ foreach ( $this->notices as $_notice_key => $_notice ) {
612
+
613
+ $notice_array = $this->get_notice_array_for_key( $_notice_key );
614
+
615
+ // remove the notice if key doesn’t exist anymore.
616
+ if ( array() === $notice_array ) {
617
+ $this->remove( $_notice_key );
618
+ }
619
+
620
+ $notice_type = isset( $notice_array['type'] ) ? $notice_array['type'] : 'problem';
621
+
622
+ // skip if type is not correct.
623
+ if ( $notice_type !== $type ) {
624
+ continue;
625
+ }
626
+
627
+ if ( ! empty( $_notice['text'] ) ) {
628
+ $text = $_notice['text'];
629
+ } elseif ( isset( $notice_array['text'] ) ) {
630
+ $text = $notice_array['text'];
631
+ } else {
632
+ continue;
633
+ }
634
+
635
+ // attach "append_text".
636
+ if ( ! empty( $_notice['append_text'] ) ) {
637
+ $text .= $_notice['append_text'];
638
+ }
639
+
640
+ // attach "get help" link.
641
+ if ( ! empty( $_notice['get_help_link'] ) ) {
642
+ $text .= $this->get_help_link( $_notice['get_help_link'] );
643
+ } elseif ( isset( $notice_array['get_help_link'] ) ) {
644
+ $text .= $this->get_help_link( $notice_array['get_help_link'] );
645
+ }
646
+
647
+ $can_hide = ( ! isset( $notice_array['can_hide'] ) || true === $notice_array['can_hide'] ) ? true : false;
648
+ $hide = ( ! isset( $notice_array['hide'] ) || true === $notice_array['hide'] ) ? true : false;
649
+ $is_hidden = in_array( $_notice_key, $this->ignore, true ) ? true : false;
650
+ $date = isset( $_notice['time'] ) ? date_i18n( get_option( 'date_format' ), $_notice['time'] ) : false;
651
+
652
+ include ADVADS_BASE_PATH . '/admin/views/overview-notice-row.php';
653
+ }
654
+
655
+ ?>
656
+ </ul>
657
+ <?php
658
  }
659
 
660
  /**
770
  $notice_array = $this->get_notice_array_for_key( $_key );
771
 
772
  if ( isset( $notice_array['type'] ) && $type === $notice_array['type']
773
+ && ( ! isset( $this->ignore ) || false === array_search( $_key, $this->ignore, true ) ) ) {
774
  $notices_by_type[ $_key ] = $_notice;
775
  }
776
  }
829
  * Add notification when an ad expires based on the expiry date
830
  *
831
  * @param integer $ad_id ID of the ad.
832
+ * @param object $ad ad object.
833
  */
834
  public function ad_expired( $ad_id, $ad ) {
835
  $id = ! empty( $ad_id ) ? absint( $ad_id ) : 0;
836
+ $this->update(
837
+ 'ad_expired',
838
+ array(
839
+ 'append_key' => $id,
840
+ 'ad_id' => $id,
841
+ )
842
+ );
843
  }
844
 
845
  /**
846
  * Get AdSense error link
847
  * this is a copy of Advanced_Ads_AdSense_MAPI::get_adsense_error_link() which might not be available all the time
848
  *
849
+ * @param string $code error code.
850
  *
851
  * @return string link
852
  */
860
  }
861
 
862
  // is a copy of Advanced_Ads_AdSense_MAPI::get_adsense_error_link().
863
+ return sprintf(
864
  // translators: %1$s is an anchor (link) opening tag, %2$s is the closing tag.
865
  esc_attr__( 'Learn more about AdSense account issues %1$shere%2$s.', 'advanced-ads' ),
866
  '<a href="' . ADVADS_URL . 'adsense-errors/#utm_source=advanced-ads&utm_medium=link&utm_campaign=adsense-error' . $code . '" target="_blank">',
867
  '</a>'
868
  );
 
 
869
  }
870
 
871
  /**
classes/ad-model.php CHANGED
@@ -17,35 +17,42 @@ class Advanced_Ads_Model {
17
  const OBJECT_CACHE_TTL = 720; // 12 Minutes
18
 
19
  /**
 
20
  *
21
  * @var wpdb
22
  */
23
  protected $db;
24
 
25
  /**
 
26
  *
27
  * @var array
28
  */
29
  protected $ad_conditions;
30
 
31
  /**
 
32
  *
33
  * @var array
34
  */
35
  protected $ad_placements;
36
 
37
- public function __construct(wpdb $wpdb)
38
- {
 
 
 
 
39
  $this->db = $wpdb;
40
- }
41
-
42
  /**
 
43
  *
44
  * @return array
45
  */
46
- public function get_ad_conditions()
47
- {
48
- if ( ! isset(self::$ad_conditions) ) {
49
  $this->ad_conditions = include ADVADS_BASE_PATH . 'includes/array_ad_conditions.php';
50
  }
51
 
@@ -62,56 +69,57 @@ class Advanced_Ads_Model {
62
  * @return array|false The blog ids, false if no matches.
63
  */
64
  public function get_blog_ids() {
65
- // get an array of blog ids
66
  $sql = "SELECT blog_id FROM $this->db->blogs WHERE archived = '0' AND spam = '0' AND deleted = '0'";
67
 
68
  return $this->db->get_col( $sql );
69
  }
70
 
71
  /**
72
- * load all ads based on WP_Query conditions
73
  *
74
  * @since 1.1.0
75
- * @param arr $args WP_Query arguments that are more specific that default
76
- * @return arr $ads array with post objects
77
  */
78
- public function get_ads($args = array()){
79
- // add default WP_Query arguments
80
- $args['post_type'] = Advanced_Ads::POST_TYPE_SLUG;
81
  $args['posts_per_page'] = -1;
82
- if ( empty($args['post_status']) ) { $args['post_status'] = array( 'publish', 'future' ); }
 
83
  $ads = new WP_Query( $args );
84
 
85
  return $ads->posts;
86
  }
87
 
88
  /**
89
- * load all ad groups
90
  *
91
  * @since 1.1.0
92
- * @param arr $args array with options
93
- * @return arr $groups array with ad groups
94
  * @link http://codex.wordpress.org/Function_Reference/get_terms
95
  */
96
- public function get_ad_groups($args = array()){
97
- $args['hide_empty'] = isset($args['hide_empty']) ? $args['hide_empty'] : false; // display groups without any ads
98
 
99
  return get_terms( Advanced_Ads::AD_GROUP_TAXONOMY, $args );
100
  }
101
 
102
  /**
103
- * get the array with ad placements
104
  *
105
  * @since 1.1.0
106
- * @return arr $ad_placements
107
  */
108
- public function get_ad_placements_array(){
109
-
110
  if ( ! isset( $this->ad_placements ) ) {
111
  $this->ad_placements = get_option( 'advads-ads-placements', array() );
112
 
113
- // load default array if not saved yet
114
- if ( ! is_array( $this->ad_placements ) ){
115
  $this->ad_placements = array();
116
  }
117
 
@@ -129,9 +137,9 @@ class Advanced_Ads_Model {
129
  }
130
 
131
  /**
132
- * update the array with ad placements
133
  *
134
- * @param arr $ad_placements
135
  */
136
  public function update_ad_placements_array( $ad_placements ) {
137
  update_option( 'advads-ads-placements', $ad_placements );
17
  const OBJECT_CACHE_TTL = 720; // 12 Minutes
18
 
19
  /**
20
+ * WordPress database object.
21
  *
22
  * @var wpdb
23
  */
24
  protected $db;
25
 
26
  /**
27
+ * General ad conditions.
28
  *
29
  * @var array
30
  */
31
  protected $ad_conditions;
32
 
33
  /**
34
+ * Placements
35
  *
36
  * @var array
37
  */
38
  protected $ad_placements;
39
 
40
+ /**
41
+ * Advanced_Ads_Model constructor.
42
+ *
43
+ * @param wpdb $wpdb WordPress database access.
44
+ */
45
+ public function __construct( wpdb $wpdb ) {
46
  $this->db = $wpdb;
47
+ }
48
+
49
  /**
50
+ * Load ad conditions.
51
  *
52
  * @return array
53
  */
54
+ public function get_ad_conditions() {
55
+ if ( ! isset( self::$ad_conditions ) ) {
 
56
  $this->ad_conditions = include ADVADS_BASE_PATH . 'includes/array_ad_conditions.php';
57
  }
58
 
69
  * @return array|false The blog ids, false if no matches.
70
  */
71
  public function get_blog_ids() {
72
+ // get an array of blog ids.
73
  $sql = "SELECT blog_id FROM $this->db->blogs WHERE archived = '0' AND spam = '0' AND deleted = '0'";
74
 
75
  return $this->db->get_col( $sql );
76
  }
77
 
78
  /**
79
+ * Load all ads based on WP_Query conditions
80
  *
81
  * @since 1.1.0
82
+ * @param array $args WP_Query arguments that are more specific that default.
83
+ * @return array $ads array with post objects.
84
  */
85
+ public function get_ads( $args = array() ) {
86
+ // add default WP_Query arguments.
87
+ $args['post_type'] = Advanced_Ads::POST_TYPE_SLUG;
88
  $args['posts_per_page'] = -1;
89
+ if ( empty( $args['post_status'] ) ) {
90
+ $args['post_status'] = array( 'publish', 'future' ); }
91
  $ads = new WP_Query( $args );
92
 
93
  return $ads->posts;
94
  }
95
 
96
  /**
97
+ * Load all ad groups
98
  *
99
  * @since 1.1.0
100
+ * @param array $args array with options.
101
+ * @return array array with ad groups
102
  * @link http://codex.wordpress.org/Function_Reference/get_terms
103
  */
104
+ public function get_ad_groups( $args = array() ) {
105
+ $args['hide_empty'] = isset( $args['hide_empty'] ) ? $args['hide_empty'] : false; // display groups without any ads.
106
 
107
  return get_terms( Advanced_Ads::AD_GROUP_TAXONOMY, $args );
108
  }
109
 
110
  /**
111
+ * Get the array with ad placements
112
  *
113
  * @since 1.1.0
114
+ * @return array $ad_placements
115
  */
116
+ public function get_ad_placements_array() {
117
+
118
  if ( ! isset( $this->ad_placements ) ) {
119
  $this->ad_placements = get_option( 'advads-ads-placements', array() );
120
 
121
+ // load default array if not saved yet.
122
+ if ( ! is_array( $this->ad_placements ) ) {
123
  $this->ad_placements = array();
124
  }
125
 
137
  }
138
 
139
  /**
140
+ * Update the array with ad placements
141
  *
142
+ * @param array $ad_placements array with placements.
143
  */
144
  public function update_ad_placements_array( $ad_placements ) {
145
  update_option( 'advads-ads-placements', $ad_placements );
classes/ad.php CHANGED
@@ -1,5 +1,4 @@
1
  <?php
2
-
3
  /**
4
  * Advanced Ads Ad.
5
  *
@@ -7,25 +6,25 @@
7
  * @author Thomas Maier <support@wpadvancedads.com>
8
  * @license GPL-2.0+
9
  * @link https://wpadvancedads.com
10
- * @copyright 2013 Thomas Maier, Advanced Ads GmbH
11
  */
12
 
13
  /**
14
- * an ad object
15
  *
16
  * @package Advanced_Ads_Ad
17
  * @author Thomas Maier <support@wpadvancedads.com>
18
  * @deprecated since version 1.5.3 (May 6th 2015)
19
  * might still be needed if some old add-ons are running somewhere
20
  */
21
- if( ! class_exists ( 'Advads_Ad', false ) ){
22
- class Advads_Ad extends Advanced_Ads_Ad {
23
 
24
- }
25
  }
26
 
27
  /**
28
- * an ad object
29
  *
30
  * @package Advanced_Ads_Ad
31
  * @author Thomas Maier <support@wpadvancedads.com>
@@ -33,117 +32,161 @@ if( ! class_exists ( 'Advads_Ad', false ) ){
33
  class Advanced_Ads_Ad {
34
 
35
  /**
36
- * id of the post type for this ad
 
 
37
  */
38
  public $id = 0;
39
 
40
  /**
41
- * true, if this is an Advanced Ads Ad post type
 
 
42
  */
43
  public $is_ad = false;
44
 
45
  /**
46
- * ad type
 
 
47
  */
48
  public $type = 'content';
49
 
50
  /**
51
- * ad width
 
 
52
  */
53
  public $width = 0;
54
 
55
  /**
56
- * target url
57
  *
58
- * @since 1.6.10
59
  */
60
  public $url = '';
61
 
62
  /**
63
- * ad height
 
 
64
  */
65
  public $height = 0;
66
 
67
  /**
68
- * object of current ad type
 
 
69
  */
70
  protected $type_obj;
71
 
72
  /**
73
- * content of the ad
 
 
74
  *
75
- * only needed for ad types using the post content field
76
  */
77
  public $content = '';
78
 
79
  /**
80
- * conditions of the ad display
 
 
81
  */
82
  public $conditions = array();
83
 
84
  /**
85
- * status of the ad (e.g. publish, pending)
 
 
86
  */
87
- public $status = array();
88
 
89
  /**
90
- * array with meta field options aka parameters
 
 
91
  */
92
  protected $options = array();
93
 
94
  /**
95
- * name of the meta field to save options to
 
 
96
  */
97
- static $options_meta_field = 'advanced_ads_ad_options';
98
 
99
  /**
100
- * additional arguments set when ad is loaded, overwrites or extends options
 
 
101
  */
102
  public $args = array();
103
 
104
  /**
105
- * multidimensional array contains information about the wrapper
106
- * each possible html attribute is an array with possible multiple elements
 
 
107
  */
108
  public $wrapper = array();
109
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  /**
111
  * Displayed above the ad.
 
 
112
  */
113
  protected $label = '';
114
 
115
  /**
116
- * init ad object
117
  *
118
- * @param int $id id of the ad (= post id)
119
- * @param arr $args additional arguments
120
  */
121
- public function __construct($id, $args = array()) {
122
- $id = absint( $id );
123
- $this->id = $id;
124
  $this->args = is_array( $args ) ? $args : array();
125
 
126
- // whether the ad will be tracked
127
  $this->global_output = isset( $this->args['global_output'] ) ? (bool) $this->args['global_output'] : true;
128
 
129
- if ( ! empty($id) ) { $this->load( $id ); }
 
 
130
 
131
- // dynamically add sanitize filters for condition types
132
  $_types = array();
133
  // -TODO use model
134
  $advanced_ads_ad_conditions = Advanced_Ads::get_ad_conditions();
135
  foreach ( $advanced_ads_ad_conditions as $_condition ) {
136
- // add unique
137
- $_types[$_condition['type']] = false;
138
  }
139
- // iterate types
140
  foreach ( array_keys( $_types ) as $_type ) {
141
  // -TODO might be faster to use __call() method or isset()-test class method array
142
- $method_name = 'sanitize_condition_'. $_type;
143
  if ( method_exists( $this, $method_name ) ) {
144
- add_filter( 'advanced-ads-sanitize-condition-' . $_type, array($this, $method_name), 10, 1 );
145
  } elseif ( function_exists( 'advads_sanitize_condition_' . $_type ) ) {
146
- // check for public function to sanitize this
147
  add_filter( 'advanced-ads-sanitize-condition-' . $_type, 'advads_sanitize_condition_' . $_type, 10, 1 );
148
 
149
  }
@@ -151,98 +194,106 @@ class Advanced_Ads_Ad {
151
  }
152
 
153
  /**
154
- * load an ad object by id based on its ad type
155
  *
156
- * @since 1.0.0
 
 
157
  */
158
- private function load($id = 0){
159
 
160
  $_data = get_post( $id );
161
- if ( $_data == null ) { return false; }
 
 
162
 
163
- // return, if not an ad
164
- if ( $_data->post_type != Advanced_Ads::POST_TYPE_SLUG ) {
165
  return false;
166
  } else {
167
  $this->is_ad = true;
168
  }
169
 
170
-
171
- $this->type = $this->options( 'type' );
172
  $this->title = $_data->post_title;
173
  /* load ad type object */
174
  $types = Advanced_Ads::get_instance()->ad_types;
175
- if ( isset($types[$this->type]) ){
176
- $this->type_obj = $types[$this->type];
177
  } else {
178
- $this->type_obj = new Advanced_Ads_Ad_Type_Abstract;
179
- }
180
- $this->url = $this->get_url();
181
- $this->width = absint( $this->options( 'width' ) );
182
- $this->height = absint( $this->options( 'height' ) );
183
- $this->conditions = $this->options( 'conditions' );
184
- $this->description = $this->options( 'description' );
185
- $this->output = $this->options( 'output' );
186
- $this->status = $_data->post_status;
187
- $this->expiry_date = $this->options( 'expiry_date' );
188
- $this->is_head_placement = isset( $this->args['placement_type'] ) && $this->args['placement_type'] === 'header';
189
  $this->args['is_top_level'] = ! isset( $this->args['is_top_level'] );
190
-
191
- // load content based on ad type
192
- $this->content = $this->type_obj->load_content( $_data );
193
 
 
 
194
 
195
  if ( ! $this->is_head_placement ) {
196
  $this->maybe_create_label();
197
  $this->wrapper = $this->load_wrapper_options();
198
 
199
- // set wrapper conditions
200
  $this->wrapper = apply_filters( 'advanced-ads-set-wrapper', $this->wrapper, $this );
201
- // add unique wrapper id
202
  if ( is_array( $this->wrapper )
203
- && $this->wrapper !== array()
204
- && ! isset( $this->wrapper['id'] ) ){
205
- // create unique id if not yet given
206
  $this->wrapper['id'] = $this->create_wrapper_id();
207
  }
208
  }
209
  }
210
 
211
  /**
212
- * get options from meta field and return specific field
 
 
 
213
  *
214
- * @param string $field post meta key to be returned
215
  * @return mixed meta field content
216
- * @since 1.0.0
217
  * @todo check against default values
218
  */
219
  public function options( $field = '', $default = null ) {
220
  // retrieve options, if not given yet
221
  // -TODO may execute multiple times (if empty); bad design and risk to access unintialised data with direct access to $this->options property.
222
- if ( $this->options === array() ) {
223
- // get_post_meta() may return false
224
  $meta = get_post_meta( $this->id, self::$options_meta_field, true );
225
  if ( $meta ) {
226
- // merge meta with arguments given on ad load
227
  $this->options = Advanced_Ads_Utils::merge_deep_array( array( $meta, $this->args ) );
228
  } else {
229
- // load arguments given on ad load
230
  $this->options = $this->args;
231
  }
232
 
233
  if ( isset( $this->options['change-ad'] ) ) {
234
- // some options was provided by the user
235
- $this->options = Advanced_Ads_Utils::merge_deep_array( array( $this->options, $this->options['change-ad'] ) );
 
 
 
 
 
236
  }
237
  }
238
-
239
- // return specific option
240
- if ( $field != '' ) {
241
- if ( isset($this->options[$field]) ) {
242
- return $this->options[$field];
243
  }
244
- } else { // return all options
245
- if ( ! empty($this->options) ) {
246
  return $this->options;
247
  }
248
  }
@@ -251,89 +302,108 @@ class Advanced_Ads_Ad {
251
  }
252
 
253
  /**
254
- * set an option of the ad
 
 
 
255
  *
256
  * @since 1.1.0
257
- * @param string $option name of the option
258
- * @param mixed $value value of the option
259
  */
260
- public function set_option($option = '', $value = ''){
261
- if ( $option == '' ) { return; }
 
 
262
 
263
- // get current options
264
  $options = $this->options();
265
 
266
- // set options
267
- $options[$option] = $value;
268
 
269
- // save options
270
  $this->options = $options;
271
 
272
  }
273
 
274
 
275
  /**
276
- * return ad content for frontend output
 
 
277
  *
278
- * @since 1.0.0
279
- * @param array $output_options output options
280
  * @return string $output ad output
 
281
  */
282
- public function output( $output_options = array() ){
283
- if ( ! $this->is_ad ) { return ''; }
 
 
284
 
285
- $output_options['global_output'] = $this->global_output = isset( $output_options['global_output'] ) ? $output_options['global_output'] : $this->global_output;
 
 
 
 
 
 
 
 
286
 
287
- // switch between normal and debug mode
288
- // check if debug output should only be displayed to admins
289
- $user_can_manage_ads = current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options') );
290
- if( isset( $this->output['debugmode'] )
291
- && ( $user_can_manage_ads || ( ! $user_can_manage_ads && ! defined('ADVANCED_ADS_AD_DEBUG_FOR_ADMIN_ONLY') ) ) ){
292
- $debug = new Advanced_Ads_Ad_Debug;
293
  return $debug->prepare_debug_output( $this );
294
  } else {
295
- $output = $this->prepare_frontend_output();
296
  }
297
 
298
- // add the ad to the global output array
299
  $advads = Advanced_Ads::get_instance();
300
  if ( $output_options['global_output'] ) {
301
- $new_ad = array('type' => 'ad', 'id' => $this->id, 'title' => $this->title, 'output' => $output);
 
 
 
 
 
302
  // if ( method_exists( 'Advanced_Ads_Tracking_Plugin' , 'check_ad_tracking_enabled' ) ) {
303
  // if ( class_exists( 'Advanced_Ads_Tracking_Plugin', false ) ) {
304
- if ( defined( 'AAT_VERSION' ) && -1 < version_compare( AAT_VERSION, '1.4.2' ) ) {
305
-
306
  $new_ad['tracking_enabled'] = Advanced_Ads_Tracking_Plugin::get_instance()->check_ad_tracking_enabled( $this );
307
-
308
  $tracking_options = Advanced_Ads_Tracking_Plugin::get_instance()->options();
309
- if ( isset( $tracking_options['method'] ) && 'frontend' == $tracking_options['method'] && isset( $this->output['placement_id'] ) ) {
310
  $new_ad['placement_id'] = $this->output['placement_id'];
311
  }
312
-
313
  }
314
-
315
  $advads->current_ads[] = $new_ad;
316
  }
317
-
318
- // action when output is created
319
  do_action( 'advanced-ads-output', $this, $output, $output_options );
320
-
321
  return apply_filters( 'advanced-ads-output-final', $output, $this, $output_options );
322
  }
323
 
324
  /**
325
- * check if the ad can be displayed in frontend due to its own conditions
 
 
326
  *
327
- * @since 1.0.0
328
- * @param array $check_options check options
329
  * @return bool $can_display true if can be displayed in frontend
 
330
  */
331
  public function can_display( $check_options = array() ) {
332
- $check_options = wp_parse_args( $check_options, array( 'passive_cache_busting' => false, 'ignore_debugmode' => false ) );
333
-
334
- // prevent ad to show up through wp_head, if this is not a header placement
335
- if( doing_action( 'wp_head' ) && isset( $this->options['placement_type'] ) && 'header' !== $this->options['placement_type'] ){
336
- return false;
 
 
 
 
 
 
337
  }
338
 
339
  // Check If the current ad is requested using a shortcode placed in the content of the current ad.
@@ -341,14 +411,14 @@ class Advanced_Ads_Ad {
341
  return false;
342
  }
343
 
344
- // force ad display if debug mode is enabled
345
- if( isset( $this->output['debugmode'] ) && ! $check_options['ignore_debugmode'] ) {
346
- return true;
347
  }
348
-
349
  if ( ! $check_options['passive_cache_busting'] ) {
350
- // don’t display ads that are not published or private for users not logged in
351
- if ( $this->status !== 'publish' && ! ($this->status === 'private' && is_user_logged_in() ) ) {
352
  return false;
353
  }
354
 
@@ -356,60 +426,63 @@ class Advanced_Ads_Ad {
356
  return false;
357
  }
358
  } else {
359
- if ( $this->status !== 'publish' || ! $this->can_display_by_expiry_date() ) {
360
  return false;
361
  }
362
  }
363
 
364
- // add own conditions to flag output as possible or not
365
  $can_display = apply_filters( 'advanced-ads-can-display', true, $this, $check_options );
366
 
367
  return $can_display;
368
  }
369
 
370
  /**
371
- * check visitor conditions
372
  *
373
- * @since 1.1.0
374
  * @return bool $can_display true if can be displayed in frontend based on visitor settings
 
375
  */
376
- public function can_display_by_visitor(){
377
  if ( ! empty( $this->options['wp_the_query']['is_feed'] ) ) {
378
  return true;
379
  }
380
 
381
- // check old "visitor" and new "visitors" conditions
382
- if ( ( empty($this->options['visitors']) ||
383
- ! is_array( $this->options['visitors'] ) )
384
- && ( empty($this->options['visitor']) ||
385
- ! is_array( $this->options['visitor'] )
386
- )) { return true; }
 
 
387
 
388
  if ( isset( $this->options['visitors'] ) && is_array( $this->options['visitors'] ) ) {
389
 
390
- $visitor_conditions = $this->options['visitors'];
391
-
392
- $last_result = false;
393
- $length = count( $visitor_conditions );
394
-
395
- for($i = 0; $i < $length; ++$i) {
396
- $_condition = current( $visitor_conditions );
397
- // ignore OR if last result was true
398
- if( $last_result && isset( $_condition['connector'] ) && 'or' === $_condition['connector'] ){
399
- next( $visitor_conditions );
400
- continue;
401
- }
402
- $last_result = $result = Advanced_Ads_Visitor_Conditions::frontend_check( $_condition, $this );
403
- if( ! $result ) {
404
- // return false only, if the next condition doesn’t have an OR operator
405
- $next = next( $visitor_conditions );
406
- if( ! isset( $next['connector'] ) || $next['connector'] !== 'or' ) {
407
- return false;
408
- }
409
- } else {
410
- next( $visitor_conditions );
 
 
411
  }
412
- }
413
  }
414
 
415
  /**
@@ -418,18 +491,24 @@ class Advanced_Ads_Ad {
418
  * @deprecated since version 1.5.4
419
  */
420
 
421
- if ( empty($this->options['visitor']) ||
422
- ! is_array( $this->options['visitor'] ) ) { return true; }
 
 
423
  $visitor_conditions = $this->options( 'visitor' );
424
 
425
- // check mobile condition
426
- if ( isset($visitor_conditions['mobile']) ){
427
- switch ( $visitor_conditions['mobile'] ){
428
- case 'only' :
429
- if ( ! wp_is_mobile() ) { return false; }
 
 
430
  break;
431
- case 'no' :
432
- if ( wp_is_mobile() ) { return false; }
 
 
433
  break;
434
  }
435
  }
@@ -438,26 +517,31 @@ class Advanced_Ads_Ad {
438
  }
439
 
440
  /**
441
- * check expiry date
442
  *
443
- * @since 1.3.15
444
  * @return bool $can_display true if can be displayed in frontend based on expiry date
 
445
  */
446
- public function can_display_by_expiry_date(){
447
 
448
- // if expiry_date is not set, null is returned
449
  $ad_expiry_date = (int) $this->options( 'expiry_date' );
450
 
451
  if ( $ad_expiry_date <= 0 || $ad_expiry_date > time() ) {
452
  return true;
453
  }
454
 
455
- // set status to 'draft' if the ad is expired
456
- if ( $this->status !== 'draft' ) {
457
- // removing the kses filters here so that expiring ads don’t lose HTML or other code.
458
- kses_remove_filters();
459
- wp_update_post( array( 'ID' => $this->id, 'post_status' => 'draft' ) );
460
- kses_init_filters();
 
 
 
 
 
461
  /**
462
  * Run when an ad expires
463
  */
@@ -468,39 +552,39 @@ class Advanced_Ads_Ad {
468
  }
469
 
470
  /**
471
- * save an ad to the database
472
  * takes values from the current state
473
  */
474
- public function save(){
475
  global $wpdb;
476
 
477
- // remove slashes from content
478
  $this->content = $this->prepare_content_to_save();
479
 
480
- $where = array('ID' => $this->id);
481
  $wpdb->update( $wpdb->posts, array( 'post_content' => $this->content ), $where );
482
 
483
- // clean post from object cache
484
  clean_post_cache( $this->id );
485
 
486
  // sanitize conditions
487
- // see sanitize_conditions function for example on using this filter
488
  $conditions = self::sanitize_conditions_on_save( $this->conditions );
489
 
490
- // save other options to post meta field
491
  $options = $this->options();
492
 
493
  $options['type'] = $this->type;
494
- $options['url'] = $this->url;
495
  // Inform the tracking add-on about the new url.
496
- unset ( $options['tracking']['link'] );
497
- $options['width'] = $this->width;
498
- $options['height'] = $this->height;
499
- $options['conditions'] = $conditions;
500
  $options['expiry_date'] = $this->expiry_date;
501
  $options['description'] = $this->description;
502
 
503
- // sanitize container ID option
504
  $options['output']['wrapper-id'] = sanitize_key( $options['output']['wrapper-id'] );
505
 
506
  // filter to manipulate options or add more to be saved
@@ -513,7 +597,7 @@ class Advanced_Ads_Ad {
513
  * Save ad options.
514
  * Meant to be used from the outside of an ad.
515
  *
516
- * @param int $ad_id post ID of the ad.
517
  * @param array $options ad options.
518
  */
519
  public static function save_ad_options( $ad_id, array $options ) {
@@ -527,7 +611,7 @@ class Advanced_Ads_Ad {
527
  }
528
 
529
  /**
530
- * native filter for content field before being saved
531
  *
532
  * @return string $content ad content
533
  * @since 1.0.0
@@ -537,48 +621,49 @@ class Advanced_Ads_Ad {
537
  $content = $this->content;
538
 
539
  // load ad type specific parameter filter
540
- // @todo this is just a hotfix for type_obj not set, yet the cause is still unknown
541
- if(is_object( $this->type_obj )){
542
- $content = $this->type_obj->sanitize_content( $content );
543
  }
544
- // apply a custom filter by ad type
545
  $content = apply_filters( 'advanced-ads-pre-ad-save-' . $this->type, $content );
546
 
547
  return $content;
548
  }
549
 
550
  /**
551
- * native filter for ad parameters before being saved
552
  *
553
- * @return arr $parameters sanitized parameters
554
  */
555
  public function prepare_parameters_to_save() {
556
 
557
  $parameters = $this->parameters;
558
- // load ad type specific parameter filter
559
  $parameters = $this->type_obj->sanitize_parameters( $parameters );
560
 
561
- // apply native WP filter for content fields
562
  return $parameters;
563
  }
564
 
565
  /**
566
- * prepare ads output
567
  *
 
568
  */
569
  public function prepare_frontend_output() {
570
  $options = $this->options();
571
 
572
  if ( isset( $options['change-ad']['content'] ) ) {
573
- // output was provided by the user
574
  $output = $options['change-ad']['content'];
575
  } else {
576
- // load ad type specific content filter
577
  $output = $this->type_obj->prepare_output( $this );
578
  }
579
 
580
- // don’t deliver anything, if main ad content is empty
581
- if ( $output == '' ) {
582
  return;
583
  }
584
 
@@ -590,49 +675,51 @@ class Advanced_Ads_Ad {
590
  $output = $this->label . $output;
591
  }
592
 
593
- // build wrapper around the ad
594
  $output = $this->add_wrapper( $output );
595
 
596
- // add a clearfix, if set
597
- if ( ( isset($this->output['clearfix'] ) && $this->output['clearfix'] )
598
- || ( ! empty( $this->args['is_top_level'] ) && ! empty( $this->args['placement_clearfix'] ) ) ) {
599
  $output .= '<br style="clear: both; display: block; float: none;"/>';
600
  }
601
  }
602
-
603
 
604
- // apply a custom filter by ad type
605
  $output = apply_filters( 'advanced-ads-ad-output', $output, $this );
606
 
607
  return $output;
608
  }
609
 
610
  /**
611
- * sanitize ad display conditions when saving the ad
 
 
612
  *
613
- * @param array $conditions conditions array send via the dashboard form for an ad
614
  * @return array with sanitized conditions
615
  * @since 1.0.0
616
  */
617
- public function sanitize_conditions_on_save($conditions = array()){
618
 
619
  global $advanced_ads_ad_conditions;
620
 
621
- if ( ! is_array( $conditions ) || $conditions == array() ) { return array(); }
 
 
622
 
623
- foreach ( $conditions as $_key => $_condition ){
624
- if ( $_key == 'postids' ){
625
  // sanitize single post conditions
626
- if ( empty($_condition['ids']) ){ // remove, if empty
627
  $_condition['include'] = array();
628
  $_condition['exclude'] = array();
629
- } elseif( isset( $_condition['method'] ) ) {
630
- switch ( $_condition['method'] ){
631
- case 'include' :
632
  $_condition['include'] = $_condition['ids'];
633
  $_condition['exclude'] = array();
634
  break;
635
- case 'exclude' :
636
  $_condition['include'] = array();
637
  $_condition['exclude'] = $_condition['ids'];
638
  break;
@@ -640,104 +727,111 @@ class Advanced_Ads_Ad {
640
  }
641
  } else {
642
  if ( ! is_array( $_condition ) ) {
643
- $_condition = trim( $_condition ); }
 
644
  if ( $_condition == '' ) {
645
- $conditions[$_key] = $_condition;
646
  continue;
647
  }
648
  }
649
- $type = ! empty($advanced_ads_ad_conditions[$_key]['type']) ? $advanced_ads_ad_conditions[$_key]['type'] : 0;
650
- if ( empty($type) ) { continue; }
 
 
651
 
652
- // dynamically apply filters for each condition used
653
- $conditions[$_key] = apply_filters( 'advanced-ads-sanitize-condition-' . $type, $_condition );
654
  }
 
655
  return $conditions;
656
  }
657
 
658
  /**
659
- * sanitize id input field(s) for pattern /1,2,3,4/
 
 
660
  *
661
- * @pararm array/string $cond input string/array
662
  * @return array/string $cond sanitized string/array
663
  */
664
- public static function sanitize_condition_idfield($cond = ''){
665
- // strip anything that is not comma or number
666
 
667
- if ( is_array( $cond ) ){
668
- foreach ( $cond as $_key => $_cond ){
669
- $cond[$_key] = preg_replace( '#[^0-9,]#', '', $_cond );
670
  }
671
  } else {
672
  $cond = preg_replace( '#[^0-9,]#', '', $cond );
673
  }
 
674
  return $cond;
675
  }
676
 
677
  /**
678
- * sanitize radio input field
679
  *
680
- * @pararm string $string input string
681
- * @return string $string sanitized string
 
682
  */
683
- public static function sanitize_condition_radio($string = ''){
684
- // only allow 0, 1 and empty
685
- return $string = preg_replace( '#[^01]#', '', $string );
686
  }
687
 
688
  /**
689
- * sanitize comma seperated text input field
690
  *
691
- * @pararm array/string $cond input string/array
692
- * @return array/string $cond sanitized string/array
 
693
  */
694
- public static function sanitize_condition_textvalues($cond = ''){
695
- // strip anything that is not comma, alphanumeric, minus and underscore
696
- if ( is_array( $cond ) ){
697
- foreach ( $cond as $_key => $_cond ){
698
- $cond[$_key] = preg_replace( '#[^0-9,A-Za-z-_]#', '', $_cond );
699
  }
700
  } else {
701
  $cond = preg_replace( '#[^0-9,A-Za-z-_]#', '', $cond );
702
  }
 
703
  return $cond;
704
  }
705
 
706
  /**
707
- * load wrapper options set with the ad
708
  *
 
709
  * @since 1.3
710
- * @return arr $wrapper options array ready to be use in add_wrapper() function
711
  */
712
- protected function load_wrapper_options(){
713
  $wrapper = array();
714
 
715
- // print_r($this->output);
716
-
717
- $position = ! empty( $this->output['position'] ) ? $this->output['position'] : '';
718
  $use_placement_pos = false;
719
 
720
  if ( $this->args['is_top_level'] ) {
721
- if ( isset($this->output['class'] ) && is_array( $this->output['class'] ) ) {
722
  $wrapper['class'] = $this->output['class'];
723
  }
724
  if ( ! empty( $this->args['placement_position'] ) ) {
725
  // If not group, Set placement position instead of ad position.
726
  $use_placement_pos = true;
727
- $position = $this->args['placement_position'];
728
  }
729
  }
730
 
731
  switch ( $position ) {
732
- case 'left' :
733
  $wrapper['style']['float'] = 'left';
734
  break;
735
- case 'right' :
736
  $wrapper['style']['float'] = 'right';
737
  break;
738
- case 'center' :
739
- if ( ! empty ( $this->output['add_wrapper_sizes'] ) ) {
740
- $wrapper['style']['margin-left'] = 'auto';
741
  $wrapper['style']['margin-right'] = 'auto';
742
 
743
  if ( $use_placement_pos ) {
@@ -747,38 +841,37 @@ class Advanced_Ads_Ad {
747
  $wrapper['style']['text-align'] = 'center';
748
  }
749
 
750
- // add css rule after wrapper to center the ad
751
- // add_filter( 'advanced-ads-output-wrapper-after-content', array( $this, 'center_ad_content' ), 10, 2 );
752
  break;
753
- case 'clearfix' :
754
  $wrapper['style']['clear'] = 'both';
755
  break;
756
  }
757
 
758
- // add manual classes
759
- if ( isset($this->output['wrapper-class']) && '' !== $this->output['wrapper-class'] ) {
760
  $classes = explode( ' ', $this->output['wrapper-class'] );
761
 
762
- foreach( $classes as $_class ){
763
  $wrapper['class'][] = sanitize_text_field( $_class );
764
  }
765
  }
766
 
767
- if ( ! empty($this->output['margin']['top']) ) {
768
  $wrapper['style']['margin-top'] = intval( $this->output['margin']['top'] ) . 'px';
769
  }
770
- if ( ! empty($this->output['margin']['right']) ) {
771
  $wrapper['style']['margin-right'] = intval( $this->output['margin']['right'] ) . 'px';
772
  }
773
- if ( ! empty($this->output['margin']['bottom']) ) {
774
  $wrapper['style']['margin-bottom'] = intval( $this->output['margin']['bottom'] ) . 'px';
775
  }
776
- if ( ! empty($this->output['margin']['left']) ) {
777
  $wrapper['style']['margin-left'] = intval( $this->output['margin']['left'] ) . 'px';
778
  }
779
 
780
- if ( ! empty ($this->output['add_wrapper_sizes'] ) ) {
781
- $wrapper['style']['width'] = intval( $this->width ) . 'px';
782
  $wrapper['style']['height'] = intval( $this->height ) . 'px';
783
  }
784
 
@@ -786,37 +879,40 @@ class Advanced_Ads_Ad {
786
  $wrapper['style']['clear'] = 'both';
787
  }
788
 
789
-
790
  return $wrapper;
791
  }
792
 
793
  /**
794
- * add a wrapper arount the ad content if wrapper information are given
 
 
795
  *
 
796
  * @since 1.1.4
797
- * @param str $ad_content content of the ad
798
- * @return str $wrapper ad within the wrapper
799
  */
800
- protected function add_wrapper($ad_content = ''){
801
  $wrapper_options = apply_filters( 'advanced-ads-output-wrapper-options', $this->wrapper, $this );
802
 
803
  if ( ( ! isset( $this->output['wrapper-id'] ) || '' === $this->output['wrapper-id'] )
804
- && $wrapper_options == array() || ! is_array( $wrapper_options ) ) { return $ad_content; }
 
 
805
 
806
- // create unique id if not yet given
807
- if ( empty($wrapper_options['id']) ){
808
- $this->wrapper['id'] = $wrapper_options['id'] = $this->create_wrapper_id();
 
809
  }
810
-
811
- // add edit button for users with the appropriate rights
812
- if( ! defined( 'ADVANCED_ADS_DISABLE_EDIT_BAR' ) && current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads') ) ){
813
  ob_start();
814
  include ADVADS_BASE_PATH . 'public/views/ad-edit-bar.php';
815
  $ad_content = ob_get_clean() . $ad_content;
816
  }
817
-
818
  // build the box
819
- $wrapper = '<div' . Advanced_Ads_Utils::build_html_attributes( $wrapper_options ) . '>';
820
  $wrapper .= apply_filters( 'advanced-ads-output-wrapper-before-content', '', $this );
821
  $wrapper .= $ad_content;
822
  $wrapper .= apply_filters( 'advanced-ads-output-wrapper-after-content', '', $this );
@@ -826,36 +922,16 @@ class Advanced_Ads_Ad {
826
  }
827
 
828
  /**
829
- * function to add css rule after the ad to center its content
830
- *
831
- * @since 1.6.9.5
832
- * @param str $output additional output in wrapper after content
833
- * @param obj $ad Advanced_Ads_Ad object
834
- * @return str $output
835
- *
836
- */
837
- /*public function center_ad_content( $output, Advanced_Ads_Ad $ad ){
838
-
839
- // no additional check needed, because the hook is only called when the ad is centered
840
- if( isset( $ad->wrapper['id'] )){
841
- // does not work with most div elements, so actually not used now
842
- $output .= '<style type="text/css">#'. $ad->wrapper['id'] . ' img, #'. $ad->wrapper['id'] . ' div { display: inline !important; }</style>';
843
- }
844
-
845
- return $output;
846
- }*/
847
-
848
- /**
849
- * create a random wrapper id
850
  *
851
- * @since 1.1.4
852
  * @return string $id random id string
 
853
  */
854
- private function create_wrapper_id(){
855
 
856
- if( isset( $this->output['wrapper-id'] )){
857
  $id = sanitize_key( $this->output['wrapper-id'] );
858
- if( '' !== $id ){
859
  return $id;
860
  }
861
  }
@@ -871,9 +947,8 @@ class Advanced_Ads_Ad {
871
  public function maybe_create_label() {
872
  $placement_state = isset( $this->args['ad_label'] ) ? $this->args['ad_label'] : 'default';
873
 
874
- if ( $this->type !== 'group' &&
875
- $label = Advanced_Ads::get_instance()->get_label( $placement_state )
876
- ) {
877
  $this->label = $label;
878
  }
879
  }
@@ -892,12 +967,20 @@ class Advanced_Ads_Ad {
892
  // If this is not the ad edit page.
893
  if ( 'post.php' !== $pagenow && 'post-new.php' !== $pagenow ) {
894
  // Remove placeholders.
895
- $this->url = str_replace( array( '[POST_ID]', '[POST_SLUG]', '[CAT_SLUG]', '[AD_ID]' ), '', $this->url );
 
 
 
 
 
 
 
 
 
896
  }
897
  }
 
898
  return $this->url;
899
  }
900
 
901
-
902
-
903
  }
1
  <?php
 
2
  /**
3
  * Advanced Ads Ad.
4
  *
6
  * @author Thomas Maier <support@wpadvancedads.com>
7
  * @license GPL-2.0+
8
  * @link https://wpadvancedads.com
9
+ * @copyright 2013-2020 Thomas Maier, Advanced Ads GmbH
10
  */
11
 
12
  /**
13
+ * An ad object
14
  *
15
  * @package Advanced_Ads_Ad
16
  * @author Thomas Maier <support@wpadvancedads.com>
17
  * @deprecated since version 1.5.3 (May 6th 2015)
18
  * might still be needed if some old add-ons are running somewhere
19
  */
20
+ if ( ! class_exists( 'Advads_Ad', false ) ) {
21
+ class Advads_Ad extends Advanced_Ads_Ad {
22
 
23
+ }
24
  }
25
 
26
  /**
27
+ * An ad object
28
  *
29
  * @package Advanced_Ads_Ad
30
  * @author Thomas Maier <support@wpadvancedads.com>
32
  class Advanced_Ads_Ad {
33
 
34
  /**
35
+ * Id of the post type for this ad
36
+ *
37
+ * @var int $id
38
  */
39
  public $id = 0;
40
 
41
  /**
42
+ * True, if this is an Advanced Ads Ad post type
43
+ *
44
+ * @var bool $is_ad
45
  */
46
  public $is_ad = false;
47
 
48
  /**
49
+ * Ad type
50
+ *
51
+ * @var string $type ad type.
52
  */
53
  public $type = 'content';
54
 
55
  /**
56
+ * Ad width
57
+ *
58
+ * @var int $width width of the ad.
59
  */
60
  public $width = 0;
61
 
62
  /**
63
+ * Target url
64
  *
65
+ * @var string $url ad URL parameter.
66
  */
67
  public $url = '';
68
 
69
  /**
70
+ * Ad height
71
+ *
72
+ * @var int $height height of the ad.
73
  */
74
  public $height = 0;
75
 
76
  /**
77
+ * Object of current ad type
78
+ *
79
+ * @var object $type_obj object of the current ad type.
80
  */
81
  protected $type_obj;
82
 
83
  /**
84
+ * Content of the ad
85
+ *
86
+ * Only needed for ad types using the post content field
87
  *
88
+ * @var string $content content of the ad.
89
  */
90
  public $content = '';
91
 
92
  /**
93
+ * Conditions of the ad display
94
+ *
95
+ * @var array $conditions display and visitor conditions.
96
  */
97
  public $conditions = array();
98
 
99
  /**
100
+ * Status of the ad (e.g. publish, pending)
101
+ *
102
+ * @var string $status status of the ad.
103
  */
104
+ public $status = '';
105
 
106
  /**
107
+ * Array with meta field options aka parameters
108
+ *
109
+ * @var array $options ad options.
110
  */
111
  protected $options = array();
112
 
113
  /**
114
+ * Name of the meta field to save options to
115
+ *
116
+ * @var string $options_meta_field under which post meta key the ad options are stored.
117
  */
118
+ public static $options_meta_field = 'advanced_ads_ad_options';
119
 
120
  /**
121
+ * Additional arguments set when ad is loaded, overwrites or extends options
122
+ *
123
+ * @var array $args
124
  */
125
  public $args = array();
126
 
127
  /**
128
+ * Multidimensional array contains information about the wrapper
129
+ * Each possible html attribute is an array with possible multiple elements
130
+ *
131
+ * @var array $wrapper options of the ad wrapper.
132
  */
133
  public $wrapper = array();
134
 
135
+ /**
136
+ * Will the ad be tracked?
137
+ *
138
+ * @var mixed $global_output
139
+ */
140
+ public $global_output;
141
+
142
+ /**
143
+ * Title of the ad
144
+ *
145
+ * @var string $title
146
+ */
147
+ public $title = '';
148
+
149
  /**
150
  * Displayed above the ad.
151
+ *
152
+ * @var string $label ad label.
153
  */
154
  protected $label = '';
155
 
156
  /**
157
+ * Init ad object
158
  *
159
+ * @param int $id id of the ad.
160
+ * @param array $args additional arguments.
161
  */
162
+ public function __construct( $id, $args = array() ) {
163
+ $id = absint( $id );
164
+ $this->id = $id;
165
  $this->args = is_array( $args ) ? $args : array();
166
 
167
+ // whether the ad will be tracked.
168
  $this->global_output = isset( $this->args['global_output'] ) ? (bool) $this->args['global_output'] : true;
169
 
170
+ if ( ! empty( $id ) ) {
171
+ $this->load( $id );
172
+ }
173
 
174
+ // dynamically add sanitize filters for condition types.
175
  $_types = array();
176
  // -TODO use model
177
  $advanced_ads_ad_conditions = Advanced_Ads::get_ad_conditions();
178
  foreach ( $advanced_ads_ad_conditions as $_condition ) {
179
+ // add unique.
180
+ $_types[ $_condition['type'] ] = false;
181
  }
182
+ // iterate types.
183
  foreach ( array_keys( $_types ) as $_type ) {
184
  // -TODO might be faster to use __call() method or isset()-test class method array
185
+ $method_name = 'sanitize_condition_' . $_type;
186
  if ( method_exists( $this, $method_name ) ) {
187
+ add_filter( 'advanced-ads-sanitize-condition-' . $_type, array( $this, $method_name ), 10, 1 );
188
  } elseif ( function_exists( 'advads_sanitize_condition_' . $_type ) ) {
189
+ // check for public function to sanitize this.
190
  add_filter( 'advanced-ads-sanitize-condition-' . $_type, 'advads_sanitize_condition_' . $_type, 10, 1 );
191
 
192
  }
194
  }
195
 
196
  /**
197
+ * Load an ad object by id based on its ad type
198
  *
199
+ * @param int $id ad id.
200
+ *
201
+ * @return bool false if ad could not be loaded.
202
  */
203
+ private function load( $id = 0 ) {
204
 
205
  $_data = get_post( $id );
206
+ if ( null === $_data ) {
207
+ return false;
208
+ }
209
 
210
+ // return, if not an ad.
211
+ if ( Advanced_Ads::POST_TYPE_SLUG !== $_data->post_type ) {
212
  return false;
213
  } else {
214
  $this->is_ad = true;
215
  }
216
 
217
+ $this->type = $this->options( 'type' );
 
218
  $this->title = $_data->post_title;
219
  /* load ad type object */
220
  $types = Advanced_Ads::get_instance()->ad_types;
221
+ if ( isset( $types[ $this->type ] ) ) {
222
+ $this->type_obj = $types[ $this->type ];
223
  } else {
224
+ $this->type_obj = new Advanced_Ads_Ad_Type_Abstract();
225
+ }
226
+ $this->url = $this->get_url();
227
+ $this->width = absint( $this->options( 'width' ) );
228
+ $this->height = absint( $this->options( 'height' ) );
229
+ $this->conditions = $this->options( 'conditions' );
230
+ $this->description = $this->options( 'description' );
231
+ $this->output = $this->options( 'output' );
232
+ $this->status = $_data->post_status;
233
+ $this->expiry_date = $this->options( 'expiry_date' );
234
+ $this->is_head_placement = isset( $this->args['placement_type'] ) && 'header' === $this->args['placement_type'];
235
  $this->args['is_top_level'] = ! isset( $this->args['is_top_level'] );
 
 
 
236
 
237
+ // load content based on ad type.
238
+ $this->content = $this->type_obj->load_content( $_data );
239
 
240
  if ( ! $this->is_head_placement ) {
241
  $this->maybe_create_label();
242
  $this->wrapper = $this->load_wrapper_options();
243
 
244
+ // set wrapper conditions.
245
  $this->wrapper = apply_filters( 'advanced-ads-set-wrapper', $this->wrapper, $this );
246
+ // add unique wrapper id.
247
  if ( is_array( $this->wrapper )
248
+ && array() !== $this->wrapper
249
+ && ! isset( $this->wrapper['id'] ) ) {
250
+ // create unique id if not yet given.
251
  $this->wrapper['id'] = $this->create_wrapper_id();
252
  }
253
  }
254
  }
255
 
256
  /**
257
+ * Get options from meta field and return specific field
258
+ *
259
+ * @param string $field post meta key to be returned.
260
+ * @param array $default default options.
261
  *
 
262
  * @return mixed meta field content
 
263
  * @todo check against default values
264
  */
265
  public function options( $field = '', $default = null ) {
266
  // retrieve options, if not given yet
267
  // -TODO may execute multiple times (if empty); bad design and risk to access unintialised data with direct access to $this->options property.
268
+ if ( array() === $this->options ) {
269
+ // may return false.
270
  $meta = get_post_meta( $this->id, self::$options_meta_field, true );
271
  if ( $meta ) {
272
+ // merge meta with arguments given on ad load.
273
  $this->options = Advanced_Ads_Utils::merge_deep_array( array( $meta, $this->args ) );
274
  } else {
275
+ // load arguments given on ad load.
276
  $this->options = $this->args;
277
  }
278
 
279
  if ( isset( $this->options['change-ad'] ) ) {
280
+ // some options was provided by the user.
281
+ $this->options = Advanced_Ads_Utils::merge_deep_array(
282
+ array(
283
+ $this->options,
284
+ $this->options['change-ad'],
285
+ )
286
+ );
287
  }
288
  }
289
+
290
+ // return specific option.
291
+ if ( '' !== $field ) {
292
+ if ( isset( $this->options[ $field ] ) ) {
293
+ return $this->options[ $field ];
294
  }
295
+ } else { // return all options.
296
+ if ( ! empty( $this->options ) ) {
297
  return $this->options;
298
  }
299
  }
302
  }
303
 
304
  /**
305
+ * Set an option of the ad
306
+ *
307
+ * @param string $option name of the option.
308
+ * @param mixed $value value of the option.
309
  *
310
  * @since 1.1.0
 
 
311
  */
312
+ public function set_option( $option = '', $value = '' ) {
313
+ if ( '' === $option ) {
314
+ return;
315
+ }
316
 
317
+ // get current options.
318
  $options = $this->options();
319
 
320
+ // set options.
321
+ $options[ $option ] = $value;
322
 
323
+ // save options.
324
  $this->options = $options;
325
 
326
  }
327
 
328
 
329
  /**
330
+ * Return ad content for frontend output
331
+ *
332
+ * @param array $output_options output options.
333
  *
 
 
334
  * @return string $output ad output
335
+ * @since 1.0.0
336
  */
337
+ public function output( $output_options = array() ) {
338
+ if ( ! $this->is_ad ) {
339
+ return '';
340
+ }
341
 
342
+ $this->global_output = isset( $output_options['global_output'] ) ? $output_options['global_output'] : $this->global_output;
343
+ $output_options['global_output'] = $this->global_output;
344
+
345
+ // switch between normal and debug mode.
346
+ // check if debug output should only be displayed to admins.
347
+ $user_can_manage_ads = current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options' ) );
348
+ if ( isset( $this->output['debugmode'] )
349
+ && ( $user_can_manage_ads || ( ! $user_can_manage_ads && ! defined( 'ADVANCED_ADS_AD_DEBUG_FOR_ADMIN_ONLY' ) ) ) ) {
350
+ $debug = new Advanced_Ads_Ad_Debug();
351
 
 
 
 
 
 
 
352
  return $debug->prepare_debug_output( $this );
353
  } else {
354
+ $output = $this->prepare_frontend_output();
355
  }
356
 
357
+ // add the ad to the global output array.
358
  $advads = Advanced_Ads::get_instance();
359
  if ( $output_options['global_output'] ) {
360
+ $new_ad = array(
361
+ 'type' => 'ad',
362
+ 'id' => $this->id,
363
+ 'title' => $this->title,
364
+ 'output' => $output,
365
+ );
366
  // if ( method_exists( 'Advanced_Ads_Tracking_Plugin' , 'check_ad_tracking_enabled' ) ) {
367
  // if ( class_exists( 'Advanced_Ads_Tracking_Plugin', false ) ) {
368
+ if ( defined( 'AAT_VERSION' ) && - 1 < version_compare( AAT_VERSION, '1.4.2' ) ) {
369
+
370
  $new_ad['tracking_enabled'] = Advanced_Ads_Tracking_Plugin::get_instance()->check_ad_tracking_enabled( $this );
371
+
372
  $tracking_options = Advanced_Ads_Tracking_Plugin::get_instance()->options();
373
+ if ( isset( $tracking_options['method'] ) && 'frontend' === $tracking_options['method'] && isset( $this->output['placement_id'] ) ) {
374
  $new_ad['placement_id'] = $this->output['placement_id'];
375
  }
 
376
  }
377
+
378
  $advads->current_ads[] = $new_ad;
379
  }
380
+
381
+ // action when output is created.
382
  do_action( 'advanced-ads-output', $this, $output, $output_options );
383
+
384
  return apply_filters( 'advanced-ads-output-final', $output, $this, $output_options );
385
  }
386
 
387
  /**
388
+ * Check if the ad can be displayed in frontend due to its own conditions
389
+ *
390
+ * @param array $check_options check options.
391
  *
 
 
392
  * @return bool $can_display true if can be displayed in frontend
393
+ * @since 1.0.0
394
  */
395
  public function can_display( $check_options = array() ) {
396
+ $check_options = wp_parse_args(
397
+ $check_options,
398
+ array(
399
+ 'passive_cache_busting' => false,
400
+ 'ignore_debugmode' => false,
401
+ )
402
+ );
403
+
404
+ // prevent ad to show up through wp_head, if this is not a header placement.
405
+ if ( doing_action( 'wp_head' ) && isset( $this->options['placement_type'] ) && 'header' !== $this->options['placement_type'] ) {
406
+ return false;
407
  }
408
 
409
  // Check If the current ad is requested using a shortcode placed in the content of the current ad.
411
  return false;
412
  }
413
 
414
+ // force ad display if debug mode is enabled.
415
+ if ( isset( $this->output['debugmode'] ) && ! $check_options['ignore_debugmode'] ) {
416
+ return true;
417
  }
418
+
419
  if ( ! $check_options['passive_cache_busting'] ) {
420
+ // don’t display ads that are not published or private for users not logged in.
421
+ if ( 'publish' !== $this->status && ! ( 'private' === $this->status && is_user_logged_in() ) ) {
422
  return false;
423
  }
424
 
426
  return false;
427
  }
428
  } else {
429
+ if ( 'publish' !== $this->status || ! $this->can_display_by_expiry_date() ) {
430
  return false;
431
  }
432
  }
433
 
434
+ // add own conditions to flag output as possible or not.
435
  $can_display = apply_filters( 'advanced-ads-can-display', true, $this, $check_options );
436
 
437
  return $can_display;
438
  }
439
 
440
  /**
441
+ * Check visitor conditions
442
  *
 
443
  * @return bool $can_display true if can be displayed in frontend based on visitor settings
444
+ * @since 1.1.0
445
  */
446
+ public function can_display_by_visitor() {
447
  if ( ! empty( $this->options['wp_the_query']['is_feed'] ) ) {
448
  return true;
449
  }
450
 
451
+ // check old "visitor" and new "visitors" conditions.
452
+ if ( ( empty( $this->options['visitors'] ) ||
453
+ ! is_array( $this->options['visitors'] ) )
454
+ && ( empty( $this->options['visitor'] ) ||
455
+ ! is_array( $this->options['visitor'] )
456
+ ) ) {
457
+ return true;
458
+ }
459
 
460
  if ( isset( $this->options['visitors'] ) && is_array( $this->options['visitors'] ) ) {
461
 
462
+ $visitor_conditions = $this->options['visitors'];
463
+
464
+ $last_result = false;
465
+ $length = count( $visitor_conditions );
466
+
467
+ for ( $i = 0; $i < $length; ++ $i ) {
468
+ $_condition = current( $visitor_conditions );
469
+ // ignore OR if last result was true.
470
+ if ( $last_result && isset( $_condition['connector'] ) && 'or' === $_condition['connector'] ) {
471
+ next( $visitor_conditions );
472
+ continue;
473
+ }
474
+ $result = Advanced_Ads_Visitor_Conditions::frontend_check( $_condition, $this );
475
+ $last_result = $result;
476
+ if ( ! $result ) {
477
+ // return false only, if the next condition doesn’t have an OR operator.
478
+ $next = next( $visitor_conditions );
479
+ if ( ! isset( $next['connector'] ) || 'or' !== $next['connector'] ) {
480
+ return false;
481
+ }
482
+ } else {
483
+ next( $visitor_conditions );
484
+ }
485
  }
 
486
  }
487
 
488
  /**
491
  * @deprecated since version 1.5.4
492
  */
493
 
494
+ if ( empty( $this->options['visitor'] ) ||
495
+ ! is_array( $this->options['visitor'] ) ) {
496
+ return true;
497
+ }
498
  $visitor_conditions = $this->options( 'visitor' );
499
 
500
+ // check mobile condition.
501
+ if ( isset( $visitor_conditions['mobile'] ) ) {
502
+ switch ( $visitor_conditions['mobile'] ) {
503
+ case 'only':
504
+ if ( ! wp_is_mobile() ) {
505
+ return false;
506
+ }
507
  break;
508
+ case 'no':
509
+ if ( wp_is_mobile() ) {
510
+ return false;
511
+ }
512
  break;
513
  }
514
  }
517
  }
518
 
519
  /**
520
+ * Check expiry date
521
  *
 
522
  * @return bool $can_display true if can be displayed in frontend based on expiry date
523
+ * @since 1.3.15
524
  */
525
+ public function can_display_by_expiry_date() {
526
 
527
+ // if expiry_date is not set, null is returned.
528
  $ad_expiry_date = (int) $this->options( 'expiry_date' );
529
 
530
  if ( $ad_expiry_date <= 0 || $ad_expiry_date > time() ) {
531
  return true;
532
  }
533
 
534
+ // set status to 'draft' if the ad is expired.
535
+ if ( 'draft' !== $this->status ) {
536
+ // removing the kses filters here so that expiring ads don’t lose HTML or other code.
537
+ kses_remove_filters();
538
+ wp_update_post(
539
+ array(
540
+ 'ID' => $this->id,
541
+ 'post_status' => 'draft',
542
+ )
543
+ );
544
+ kses_init_filters();
545
  /**
546
  * Run when an ad expires
547
  */
552
  }
553
 
554
  /**
555
+ * Save an ad to the database
556
  * takes values from the current state
557
  */
558
+ public function save() {
559
  global $wpdb;
560
 
561
+ // remove slashes from content.
562
  $this->content = $this->prepare_content_to_save();
563
 
564
+ $where = array( 'ID' => $this->id );
565
  $wpdb->update( $wpdb->posts, array( 'post_content' => $this->content ), $where );
566
 
567
+ // clean post from object cache.
568
  clean_post_cache( $this->id );
569
 
570
  // sanitize conditions
571
+ // see sanitize_conditions function for example on using this filter.
572
  $conditions = self::sanitize_conditions_on_save( $this->conditions );
573
 
574
+ // save other options to post meta field.
575
  $options = $this->options();
576
 
577
  $options['type'] = $this->type;
578
+ $options['url'] = $this->url;
579
  // Inform the tracking add-on about the new url.
580
+ unset( $options['tracking']['link'] );
581
+ $options['width'] = $this->width;
582
+ $options['height'] = $this->height;
583
+ $options['conditions'] = $conditions;
584
  $options['expiry_date'] = $this->expiry_date;
585
  $options['description'] = $this->description;
586
 
587
+ // sanitize container ID option.
588
  $options['output']['wrapper-id'] = sanitize_key( $options['output']['wrapper-id'] );
589
 
590
  // filter to manipulate options or add more to be saved
597
  * Save ad options.
598
  * Meant to be used from the outside of an ad.
599
  *
600
+ * @param int $ad_id post ID of the ad.
601
  * @param array $options ad options.
602
  */
603
  public static function save_ad_options( $ad_id, array $options ) {
611
  }
612
 
613
  /**
614
+ * Native filter for content field before being saved
615
  *
616
  * @return string $content ad content
617
  * @since 1.0.0
621
  $content = $this->content;
622
 
623
  // load ad type specific parameter filter
624
+ // @todo this is just a hotfix for type_obj not set, yet the cause is still unknown.
625
+ if ( is_object( $this->type_obj ) ) {
626
+ $content = $this->type_obj->sanitize_content( $content );
627
  }
628
+ // apply a custom filter by ad type.
629
  $content = apply_filters( 'advanced-ads-pre-ad-save-' . $this->type, $content );
630
 
631
  return $content;
632
  }
633
 
634
  /**
635
+ * Native filter for ad parameters before being saved
636
  *
637
+ * @return array $parameters sanitized parameters.
638
  */
639
  public function prepare_parameters_to_save() {
640
 
641
  $parameters = $this->parameters;
642
+ // load ad type specific parameter filter.
643
  $parameters = $this->type_obj->sanitize_parameters( $parameters );
644
 
645
+ // apply native WP filter for content fields.
646
  return $parameters;
647
  }
648
 
649
  /**
650
+ * Prepare ads output
651
  *
652
+ * @return string.
653
  */
654
  public function prepare_frontend_output() {
655
  $options = $this->options();
656
 
657
  if ( isset( $options['change-ad']['content'] ) ) {
658
+ // output was provided by the user.
659
  $output = $options['change-ad']['content'];
660
  } else {
661
+ // load ad type specific content filter.
662
  $output = $this->type_obj->prepare_output( $this );
663
  }
664
 
665
+ // don’t deliver anything, if main ad content is empty.
666
+ if ( empty( $output ) ) {
667
  return;
668
  }
669
 
675
  $output = $this->label . $output;
676
  }
677
 
678
+ // build wrapper around the ad.
679
  $output = $this->add_wrapper( $output );
680
 
681
+ // add a clearfix, if set.
682
+ if ( ( isset( $this->output['clearfix'] ) && $this->output['clearfix'] )
683
+ || ( ! empty( $this->args['is_top_level'] ) && ! empty( $this->args['placement_clearfix'] ) ) ) {
684
  $output .= '<br style="clear: both; display: block; float: none;"/>';
685
  }
686
  }
 
687
 
688
+ // apply a custom filter by ad type.
689
  $output = apply_filters( 'advanced-ads-ad-output', $output, $this );
690
 
691
  return $output;
692
  }
693
 
694
  /**
695
+ * Sanitize ad display conditions when saving the ad
696
+ *
697
+ * @param array $conditions conditions array send via the dashboard form for an ad.
698
  *
 
699
  * @return array with sanitized conditions
700
  * @since 1.0.0
701
  */
702
+ public function sanitize_conditions_on_save( $conditions = array() ) {
703
 
704
  global $advanced_ads_ad_conditions;
705
 
706
+ if ( ! is_array( $conditions ) || array() === $conditions ) {
707
+ return array();
708
+ }
709
 
710
+ foreach ( $conditions as $_key => $_condition ) {
711
+ if ( 'postids' === $_key ) {
712
  // sanitize single post conditions
713
+ if ( empty( $_condition['ids'] ) ) { // remove, if empty.
714
  $_condition['include'] = array();
715
  $_condition['exclude'] = array();
716
+ } elseif ( isset( $_condition['method'] ) ) {
717
+ switch ( $_condition['method'] ) {
718
+ case 'include':
719
  $_condition['include'] = $_condition['ids'];
720
  $_condition['exclude'] = array();
721
  break;
722
+ case 'exclude':
723
  $_condition['include'] = array();
724
  $_condition['exclude'] = $_condition['ids'];
725
  break;
727
  }
728
  } else {
729
  if ( ! is_array( $_condition ) ) {
730
+ $_condition = trim( $_condition );
731
+ }
732
  if ( $_condition == '' ) {
733
+ $conditions[ $_key ] = $_condition;
734
  continue;
735
  }
736
  }
737
+ $type = ! empty( $advanced_ads_ad_conditions[ $_key ]['type'] ) ? $advanced_ads_ad_conditions[ $_key ]['type'] : 0;
738
+ if ( empty( $type ) ) {
739
+ continue;
740
+ }
741
 
742
+ // dynamically apply filters for each condition used.
743
+ $conditions[ $_key ] = apply_filters( 'advanced-ads-sanitize-condition-' . $type, $_condition );
744
  }
745
+
746
  return $conditions;
747
  }
748
 
749
  /**
750
+ * Sanitize id input field(s) for pattern /1,2,3,4/
751
+ *
752
+ * @param mixed $cond input string/array.
753
  *
 
754
  * @return array/string $cond sanitized string/array
755
  */
756
+ public static function sanitize_condition_idfield( $cond = '' ) {
757
+ // strip anything that is not comma or number.
758
 
759
+ if ( is_array( $cond ) ) {
760
+ foreach ( $cond as $_key => $_cond ) {
761
+ $cond[ $_key ] = preg_replace( '#[^0-9,]#', '', $_cond );
762
  }
763
  } else {
764
  $cond = preg_replace( '#[^0-9,]#', '', $cond );
765
  }
766
+
767
  return $cond;
768
  }
769
 
770
  /**
771
+ * Sanitize radio input field
772
  *
773
+ * @param string $string input string.
774
+ *
775
+ * @return string $string sanitized string.
776
  */
777
+ public static function sanitize_condition_radio( $string = '' ) {
778
+ // only allow 0, 1 and empty.
779
+ return preg_replace( '#[^01]#', '', $string );
780
  }
781
 
782
  /**
783
+ * Sanitize comma seperated text input field
784
  *
785
+ * @param mixed $cond input string/array.
786
+ *
787
+ * @return array/string $cond sanitized string/array.
788
  */
789
+ public static function sanitize_condition_textvalues( $cond = '' ) {
790
+ // strip anything that is not comma, alphanumeric, minus and underscore.
791
+ if ( is_array( $cond ) ) {
792
+ foreach ( $cond as $_key => $_cond ) {
793
+ $cond[ $_key ] = preg_replace( '#[^0-9,A-Za-z-_]#', '', $_cond );
794
  }
795
  } else {
796
  $cond = preg_replace( '#[^0-9,A-Za-z-_]#', '', $cond );
797
  }
798
+
799
  return $cond;
800
  }
801
 
802
  /**
803
+ * Load wrapper options set with the ad
804
  *
805
+ * @return array $wrapper options array ready to be use in add_wrapper() function.
806
  * @since 1.3
 
807
  */
808
+ protected function load_wrapper_options() {
809
  $wrapper = array();
810
 
811
+ $position = ! empty( $this->output['position'] ) ? $this->output['position'] : '';
 
 
812
  $use_placement_pos = false;
813
 
814
  if ( $this->args['is_top_level'] ) {
815
+ if ( isset( $this->output['class'] ) && is_array( $this->output['class'] ) ) {
816
  $wrapper['class'] = $this->output['class'];
817
  }
818
  if ( ! empty( $this->args['placement_position'] ) ) {
819
  // If not group, Set placement position instead of ad position.
820
  $use_placement_pos = true;
821
+ $position = $this->args['placement_position'];
822
  }
823
  }
824
 
825
  switch ( $position ) {
826
+ case 'left':
827
  $wrapper['style']['float'] = 'left';
828
  break;
829
+ case 'right':
830
  $wrapper['style']['float'] = 'right';
831
  break;
832
+ case 'center':
833
+ if ( ! empty( $this->output['add_wrapper_sizes'] ) ) {
834
+ $wrapper['style']['margin-left'] = 'auto';
835
  $wrapper['style']['margin-right'] = 'auto';
836
 
837
  if ( $use_placement_pos ) {
841
  $wrapper['style']['text-align'] = 'center';
842
  }
843
 
844
+ // add css rule after wrapper to center the ad.
 
845
  break;
846
+ case 'clearfix':
847
  $wrapper['style']['clear'] = 'both';
848
  break;
849
  }
850
 
851
+ // add manual classes.
852
+ if ( isset( $this->output['wrapper-class'] ) && '' !== $this->output['wrapper-class'] ) {
853
  $classes = explode( ' ', $this->output['wrapper-class'] );
854
 
855
+ foreach ( $classes as $_class ) {
856
  $wrapper['class'][] = sanitize_text_field( $_class );
857
  }
858
  }
859
 
860
+ if ( ! empty( $this->output['margin']['top'] ) ) {
861
  $wrapper['style']['margin-top'] = intval( $this->output['margin']['top'] ) . 'px';
862
  }
863
+ if ( ! empty( $this->output['margin']['right'] ) ) {
864
  $wrapper['style']['margin-right'] = intval( $this->output['margin']['right'] ) . 'px';
865
  }
866
+ if ( ! empty( $this->output['margin']['bottom'] ) ) {
867
  $wrapper['style']['margin-bottom'] = intval( $this->output['margin']['bottom'] ) . 'px';
868
  }
869
+ if ( ! empty( $this->output['margin']['left'] ) ) {
870
  $wrapper['style']['margin-left'] = intval( $this->output['margin']['left'] ) . 'px';
871
  }
872
 
873
+ if ( ! empty( $this->output['add_wrapper_sizes'] ) ) {
874
+ $wrapper['style']['width'] = intval( $this->width ) . 'px';
875
  $wrapper['style']['height'] = intval( $this->height ) . 'px';
876
  }
877
 
879
  $wrapper['style']['clear'] = 'both';
880
  }
881
 
 
882
  return $wrapper;
883
  }
884
 
885
  /**
886
+ * Add a wrapper arount the ad content if wrapper information are given
887
+ *
888
+ * @param string $ad_content content of the ad.
889
  *
890
+ * @return string $wrapper ad within the wrapper
891
  * @since 1.1.4
 
 
892
  */
893
+ protected function add_wrapper( $ad_content = '' ) {
894
  $wrapper_options = apply_filters( 'advanced-ads-output-wrapper-options', $this->wrapper, $this );
895
 
896
  if ( ( ! isset( $this->output['wrapper-id'] ) || '' === $this->output['wrapper-id'] )
897
+ && array() === $wrapper_options || ! is_array( $wrapper_options ) ) {
898
+ return $ad_content;
899
+ }
900
 
901
+ // create unique id if not yet given.
902
+ if ( empty( $wrapper_options['id'] ) ) {
903
+ $wrapper_options['id'] = $this->create_wrapper_id();
904
+ $this->wrapper['id'] = $wrapper_options['id'];
905
  }
906
+
907
+ // add edit button for users with the appropriate rights.
908
+ if ( ! defined( 'ADVANCED_ADS_DISABLE_EDIT_BAR' ) && current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads' ) ) ) {
909
  ob_start();
910
  include ADVADS_BASE_PATH . 'public/views/ad-edit-bar.php';
911
  $ad_content = ob_get_clean() . $ad_content;
912
  }
913
+
914
  // build the box
915
+ $wrapper = '<div' . Advanced_Ads_Utils::build_html_attributes( $wrapper_options ) . '>';
916
  $wrapper .= apply_filters( 'advanced-ads-output-wrapper-before-content', '', $this );
917
  $wrapper .= $ad_content;
918
  $wrapper .= apply_filters( 'advanced-ads-output-wrapper-after-content', '', $this );
922
  }
923
 
924
  /**
925
+ * Create a random wrapper id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
926
  *
 
927
  * @return string $id random id string
928
+ * @since 1.1.4
929
  */
930
+ private function create_wrapper_id() {
931
 
932
+ if ( isset( $this->output['wrapper-id'] ) ) {
933
  $id = sanitize_key( $this->output['wrapper-id'] );
934
+ if ( '' !== $id ) {
935
  return $id;
936
  }
937
  }
947
  public function maybe_create_label() {
948
  $placement_state = isset( $this->args['ad_label'] ) ? $this->args['ad_label'] : 'default';
949
 
950
+ $label = Advanced_Ads::get_instance()->get_label( $placement_state );
951
+ if ( 'group' !== $this->type && $label ) {
 
952
  $this->label = $label;
953
  }
954
  }
967
  // If this is not the ad edit page.
968
  if ( 'post.php' !== $pagenow && 'post-new.php' !== $pagenow ) {
969
  // Remove placeholders.
970
+ $this->url = str_replace(
971
+ array(
972
+ '[POST_ID]',
973
+ '[POST_SLUG]',
974
+ '[CAT_SLUG]',
975
+ '[AD_ID]',
976
+ ),
977
+ '',
978
+ $this->url
979
+ );
980
  }
981
  }
982
+
983
  return $this->url;
984
  }
985
 
 
 
986
  }
classes/ad_ajax_callbacks.php CHANGED
@@ -18,10 +18,12 @@
18
  */
19
  class Advanced_Ads_Ad_Ajax_Callbacks {
20
 
 
 
 
21
  public function __construct() {
22
 
23
- // NOTE: admin only!
24
- //add_action( 'wp_ajax_load_content_editor', array( $this, 'load_content_editor' ) );
25
  add_action( 'wp_ajax_load_ad_parameters_metabox', array( $this, 'load_ad_parameters_metabox' ) );
26
  add_action( 'wp_ajax_load_visitor_conditions_metabox', array( $this, 'load_visitor_condition' ) );
27
  add_action( 'wp_ajax_load_display_conditions_metabox', array( $this, 'load_display_condition' ) );
@@ -45,33 +47,34 @@ class Advanced_Ads_Ad_Ajax_Callbacks {
45
  }
46
 
47
  /**
48
- * load content of the ad parameter metabox
49
  *
50
  * @since 1.0.0
51
  */
52
  public function load_ad_parameters_metabox() {
53
-
54
- check_ajax_referer('advanced-ads-admin-ajax-nonce', 'nonce');
55
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads') ) ) {
56
  return;
57
  }
58
 
59
- $types = Advanced_Ads::get_instance()->ad_types;
60
  $type_string = $_REQUEST['ad_type'];
61
- $ad_id = absint( $_REQUEST['ad_id'] );
62
- if ( empty($ad_id) ) { die(); }
 
63
 
64
  $ad = new Advanced_Ads_Ad( $ad_id );
65
 
66
- if ( ! empty($types[$type_string]) && method_exists( $types[$type_string], 'render_parameters' ) ) {
67
  $type = $types[ $type_string ];
68
  $type->render_parameters( $ad );
69
 
70
- $types_without_size = array('dummy');
71
- $types_without_size = apply_filters( 'advanced-ads-types-without-size', $types_without_size );
72
- if ( ! in_array($type_string, $types_without_size) ) {
73
- include ADVADS_BASE_PATH . 'admin/views/ad-parameters-size.php';
74
- }
75
  }
76
 
77
  die();
@@ -79,107 +82,112 @@ class Advanced_Ads_Ad_Ajax_Callbacks {
79
  }
80
 
81
  /**
82
- * load interface for single visitor condition
83
  *
84
  * @since 1.5.4
85
  */
86
  public function load_visitor_condition() {
87
-
88
- check_ajax_referer('advanced-ads-admin-ajax-nonce', 'nonce');
89
-
90
- if( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads') ) ) {
91
- return;
92
  }
93
 
94
- // get visitor condition types
95
  $visitor_conditions = Advanced_Ads_Visitor_Conditions::get_instance()->conditions;
96
- $condition = array();
97
- $condition['type'] = isset( $_POST['type'] ) ? $_POST['type'] : '';
98
- $index = isset( $_POST['index'] ) ? $_POST['index'] : 0;
99
 
100
  $form_name = isset( $_POST['form_name'] ) ? $_POST['form_name'] : Advanced_Ads_Visitor_Conditions::FORM_NAME;
101
 
102
- if( isset( $visitor_conditions[$condition['type']] ) ) {
103
- $metabox = $visitor_conditions[$condition['type']]['metabox'];
104
  } else {
105
- die();
106
  }
107
 
108
  if ( method_exists( $metabox[0], $metabox[1] ) ) {
109
- call_user_func( array($metabox[0], $metabox[1]), $condition, $index, $form_name );
110
  }
111
 
112
  die();
113
  }
 
114
  /**
115
- * load interface for single display condition
116
  *
117
  * @since 1.7
118
  */
119
  public function load_display_condition() {
120
-
121
- check_ajax_referer('advanced-ads-admin-ajax-nonce', 'nonce');
122
-
123
- if( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads') ) ) {
124
- return;
125
  }
126
 
127
- // get display condition types
128
- $conditions = Advanced_Ads_Display_Conditions::get_instance()->conditions;
129
- $condition = array();
130
  $condition['type'] = isset( $_POST['type'] ) ? $_POST['type'] : '';
131
- $index = isset( $_POST['index'] ) ? $_POST['index'] : 0;
132
 
133
  $form_name = isset( $_POST['form_name'] ) ? $_POST['form_name'] : Advanced_Ads_Display_Conditions::FORM_NAME;
134
 
135
- if( isset( $conditions[$condition['type']] ) ) {
136
- $metabox = $conditions[$condition['type']]['metabox'];
137
  } else {
138
- die();
139
  }
140
 
141
  if ( method_exists( $metabox[0], $metabox[1] ) ) {
142
- call_user_func( array($metabox[0], $metabox[1]), $condition, $index, $form_name );
143
  }
144
 
145
  die();
146
  }
147
 
148
- /**
149
- * search terms belonging to a specific taxonomy
150
- *
151
- * @sinc 1.4.7
152
- */
153
- public function search_terms(){
154
-
155
- check_ajax_referer('advanced-ads-admin-ajax-nonce', 'nonce');
156
-
157
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads') ) ) {
158
- return;
159
- }
160
-
161
- $args = array();
162
- $taxonomy = $_POST['tax'];
163
- $args = array('hide_empty' => false, 'number' => 20);
164
-
165
- if ( !isset( $_POST['search'] ) || $_POST['search'] === '' ) { die(); }
166
-
167
- // if search is an id, search for the term id, else do a full text search
168
- if( 0 !== absint($_POST['search'] ) && strlen( $_POST['search'] ) == strlen ( absint($_POST['search'] ) ) ){
169
- $args['include'] = array(absint($_POST['search']));
170
- } else {
171
- $args['search'] = $_POST['search'];
172
- }
173
-
174
- $results = get_terms( $taxonomy, $args );
175
- // $results = _WP_Editors::wp_link_query( $args );
176
- echo wp_json_encode( $results );
177
- echo "\n";
178
- die();
179
- }
 
 
 
 
180
 
181
  /**
182
- * close a notice for good
183
  *
184
  * @since 1.5.3
185
  */
@@ -202,27 +210,27 @@ class Advanced_Ads_Ad_Ajax_Callbacks {
202
  die();
203
  }
204
 
205
- /**
206
- * hide a notice for some time (7 days right now)
207
- *
208
- * @since 1.8.17
209
- */
210
- public function hide_notice(){
211
-
212
- check_ajax_referer('advanced-ads-admin-ajax-nonce', 'nonce');
213
-
214
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options') )
215
- || empty( $_POST['notice'] )
216
  ) {
217
- die();
218
- }
219
 
220
- Advanced_Ads_Admin_Notices::get_instance()->hide_notice( $_POST['notice'] );
221
- die();
222
- }
223
 
224
  /**
225
- * subscribe to newsletter
226
  *
227
  * @since 1.5.3
228
  */
@@ -232,79 +240,87 @@ class Advanced_Ads_Ad_Ajax_Callbacks {
232
 
233
  if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_see_interface' ) ) || empty( $_POST['notice'] )
234
  ) {
235
- wp_send_json_error( [
236
- 'message' => sprintf( __( 'An error occurred. Please use <a href="%s" target="_blank">this form</a> to sign up.', 'advanced-ads' ), 'http://eepurl.com/bk4z4P' ),
237
- ], 400 );
 
 
 
 
238
  }
239
 
240
- wp_send_json_success( [ 'message' => Advanced_Ads_Admin_Notices::get_instance()->subscribe( $_POST['notice'] ) ] );
241
  }
242
 
243
  /**
244
- * activate license of an add-on
245
  *
246
  * @since 1.5.7
247
  */
248
- public function activate_license(){
249
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options') ) ) {
250
- return;
251
- }
252
 
253
- // check nonce
254
- check_ajax_referer( 'advads_ajax_license_nonce', 'security' );
255
 
256
- if ( !isset( $_POST['addon'] ) || $_POST['addon'] === '' ) { die(); }
 
257
 
258
- echo Advanced_Ads_Admin_Licenses::get_instance()->activate_license( $_POST['addon'], $_POST['pluginname'], $_POST['optionslug'], $_POST['license'] );
 
 
259
 
260
- die();
261
  }
262
-
263
  /**
264
- * deactivate license of an add-on
265
  *
266
  * @since 1.6.11
267
  */
268
- public function deactivate_license(){
269
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options') ) ) {
270
- return;
271
- }
272
 
273
- // check nonce
274
- check_ajax_referer( 'advads_ajax_license_nonce', 'security' );
275
 
276
- if ( !isset( $_POST['addon'] ) || $_POST['addon'] === '' ) { die(); }
 
277
 
278
- echo Advanced_Ads_Admin_Licenses::get_instance()->deactivate_license( $_POST['addon'], $_POST['pluginname'], $_POST['optionslug'] );
 
 
279
 
280
- die();
281
  }
282
 
283
  /**
284
- * rebuild assets for ad-blocker module
285
- *
286
  */
287
- public function adblock_rebuild_assets(){
288
-
289
- check_ajax_referer('advanced-ads-admin-ajax-nonce', 'nonce');
290
-
291
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options') ) ) {
292
- return;
293
- }
294
-
295
- Advanced_Ads_Ad_Blocker_Admin::get_instance()->add_asset_rebuild_form();
296
- die();
297
  }
298
 
299
  /**
300
- * post search (used in Display conditions)
301
- *
302
  */
303
- public function post_search(){
304
-
305
- check_ajax_referer('advanced-ads-admin-ajax-nonce', 'nonce');
306
-
307
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads') ) ) {
308
  return;
309
  }
310
 
@@ -313,24 +329,25 @@ class Advanced_Ads_Ad_Ajax_Callbacks {
313
 
314
  wp_ajax_wp_link_ajax();
315
  }
316
-
317
  /**
318
- * inject an ad and a placement
319
- *
320
  * @since 1.7.3
321
  */
322
- public function inject_placement(){
323
-
324
- check_ajax_referer('advanced-ads-admin-ajax-nonce', 'nonce');
325
-
326
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads') ) ) {
327
  die();
328
  }
329
-
330
  $ad_id = absint( $_REQUEST['ad_id'] );
331
- if ( empty( $ad_id ) ) { die(); }
332
-
333
- // use existing placement
 
334
  if ( isset( $_REQUEST['placement_slug'] ) ) {
335
  $xml_array[] = '<placements type="array">';
336
  $xml_array[] = '<item key="0" type="array">';
@@ -344,163 +361,160 @@ class Advanced_Ads_Ad_Ajax_Callbacks {
344
 
345
  Advanced_Ads_Import::get_instance()->import( $xml );
346
  if ( count( Advanced_Ads_Import::get_instance()->imported_data['placements'] ) ) {
347
- // if the ad was assigned
348
- echo $_REQUEST['placement_slug'];
349
  };
350
  die();
351
  }
352
 
353
- // create new placement
354
- $placements = Advanced_Ads::get_instance()->get_model()->get_ad_placements_array();
355
 
356
- $type = esc_attr( $_REQUEST['placement_type'] );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
 
358
- $item = 'ad_' . $ad_id;
359
-
360
- $options = array();
361
-
362
- // check type
363
- $placement_types = Advanced_Ads_Placements::get_placement_types();
364
- if( ! isset( $placement_types[ $type ] ) ){
365
- die();
366
- }
367
-
368
- $title = $placement_types[ $type ]['title'];
369
-
370
- $new_placement = array(
371
- 'type' => $type,
372
- 'item' => $item,
373
- 'name' => $title,
374
- );
375
-
376
- // set content specific options
377
- if( 'post_content' === $type ){
378
- $index = isset( $_REQUEST['options']['index'] ) ? absint( $_REQUEST['options']['index'] ) : 1;
379
- $new_placement['options'] = array(
380
- 'position' => 'after',
381
- 'index' => $index,
382
- 'tag' => 'p'
383
- );
384
- }
385
-
386
  $slug = Advanced_Ads_Placements::save_new_placement( $new_placement );
387
- // return potential slug
388
- echo $slug;
389
-
390
- die();
391
  }
392
-
393
  /**
394
- * save ad wizard state for each user individually
395
- *
396
  * @since 1.7.4
397
  */
398
- public function save_wizard_state(){
399
-
400
- check_ajax_referer('advanced-ads-admin-ajax-nonce', 'nonce');
401
-
402
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads') ) ) {
403
- return;
404
- }
405
-
406
- $state = ( isset( $_REQUEST['hide_wizard'] ) && 'true' === $_REQUEST['hide_wizard'] ) ? 'true' : 'false';
407
-
408
- // get current user
409
- $user_id = get_current_user_id();
410
- if( ! $user_id ) {
 
 
 
 
 
411
  die();
412
- }
413
-
414
- update_user_meta( $user_id, 'advanced-ads-hide-wizard', $state );
415
-
416
- die();
417
  }
418
 
419
  /**
420
  * Enable Adsense Auto ads, previously "Page-Level ads"
421
  */
422
- public function adsense_enable_pla(){
423
 
424
  check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
425
 
426
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options') ) ) {
427
  return;
428
  }
429
 
430
- $options = get_option( GADSENSE_OPT_NAME, array() );
431
  $options['page-level-enabled'] = true;
432
  update_option( GADSENSE_OPT_NAME, $options );
433
- die();
434
  }
435
-
436
  /**
437
  * Display list of Ad Health notices
438
  */
439
- public function ad_health_notice_display(){
440
-
441
  check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
442
-
443
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options') ) ) {
444
  return;
445
  }
446
-
447
  Advanced_Ads_Ad_Health_Notices::get_instance()->render_widget();
448
  die();
449
  }
450
-
451
  /**
452
  * Push an Ad Health notice to the queue
453
  */
454
- public function ad_health_notice_push(){
455
-
456
  check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
457
-
458
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options') ) ) {
459
  return;
460
  }
461
-
462
- $key = ( !empty( $_REQUEST['key'] ) ) ? esc_attr( $_REQUEST['key'] ) : false;
463
- $attr = ( !empty( $_REQUEST['attr'] ) && is_array( $_REQUEST['attr'] ) ) ? $_REQUEST['attr'] : array();
464
-
465
  // update or new entry?
466
- if( isset( $attr['mode'] ) && 'update' === $attr['mode'] ){
467
  Advanced_Ads_Ad_Health_Notices::get_instance()->update( $key, $attr );
468
  } else {
469
  Advanced_Ads_Ad_Health_Notices::get_instance()->add( $key, $attr );
470
- }
471
-
472
  die();
473
  }
474
-
475
  /**
476
  * Hide Ad Health notice
477
  */
478
- public function ad_health_notice_hide(){
479
  check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
480
-
481
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options') ) ) {
482
  return;
483
  }
484
-
485
- $notice_key = ( !empty( $_REQUEST['notice'] ) ) ? esc_attr( $_REQUEST['notice'] ) : false;
486
-
487
  Advanced_Ads_Ad_Health_Notices::get_instance()->hide( $notice_key );
488
  die();
489
  }
490
-
491
  /**
492
  * Show all ignored notices of a given type
493
  */
494
- public function ad_health_notice_unignore(){
495
  check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
496
-
497
- if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options') ) ) {
498
  return;
499
  }
500
-
501
- // $notice_key = ( !empty( $_REQUEST['type'] ) ) ? esc_attr( $_REQUEST['type'] ) : false;
502
-
503
- // Advanced_Ads_Ad_Health_Notices::get_instance()->unignore_by_type( $notice_key );
504
  Advanced_Ads_Ad_Health_Notices::get_instance()->unignore();
505
  die();
506
  }
18
  */
19
  class Advanced_Ads_Ad_Ajax_Callbacks {
20
 
21
+ /**
22
+ * Advanced_Ads_Ad_Ajax_Callbacks constructor.
23
+ */
24
  public function __construct() {
25
 
26
+ // admin only!
 
27
  add_action( 'wp_ajax_load_ad_parameters_metabox', array( $this, 'load_ad_parameters_metabox' ) );
28
  add_action( 'wp_ajax_load_visitor_conditions_metabox', array( $this, 'load_visitor_condition' ) );
29
  add_action( 'wp_ajax_load_display_conditions_metabox', array( $this, 'load_display_condition' ) );
47
  }
48
 
49
  /**
50
+ * Load content of the ad parameter metabox
51
  *
52
  * @since 1.0.0
53
  */
54
  public function load_ad_parameters_metabox() {
55
+
56
+ check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
57
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads' ) ) ) {
58
  return;
59
  }
60
 
61
+ $types = Advanced_Ads::get_instance()->ad_types;
62
  $type_string = $_REQUEST['ad_type'];
63
+ $ad_id = absint( $_REQUEST['ad_id'] );
64
+ if ( empty( $ad_id ) ) {
65
+ die(); }
66
 
67
  $ad = new Advanced_Ads_Ad( $ad_id );
68
 
69
+ if ( ! empty( $types[ $type_string ] ) && method_exists( $types[ $type_string ], 'render_parameters' ) ) {
70
  $type = $types[ $type_string ];
71
  $type->render_parameters( $ad );
72
 
73
+ $types_without_size = array( 'dummy' );
74
+ $types_without_size = apply_filters( 'advanced-ads-types-without-size', $types_without_size );
75
+ if ( ! in_array( $type_string, $types_without_size ) ) {
76
+ include ADVADS_BASE_PATH . 'admin/views/ad-parameters-size.php';
77
+ }
78
  }
79
 
80
  die();
82
  }
83
 
84
  /**
85
+ * Load interface for single visitor condition
86
  *
87
  * @since 1.5.4
88
  */
89
  public function load_visitor_condition() {
90
+
91
+ check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
92
+
93
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads' ) ) ) {
94
+ return;
95
  }
96
 
97
+ // get visitor condition types.
98
  $visitor_conditions = Advanced_Ads_Visitor_Conditions::get_instance()->conditions;
99
+ $condition = array();
100
+ $condition['type'] = isset( $_POST['type'] ) ? $_POST['type'] : '';
101
+ $index = isset( $_POST['index'] ) ? $_POST['index'] : 0;
102
 
103
  $form_name = isset( $_POST['form_name'] ) ? $_POST['form_name'] : Advanced_Ads_Visitor_Conditions::FORM_NAME;
104
 
105
+ if ( isset( $visitor_conditions[ $condition['type'] ] ) ) {
106
+ $metabox = $visitor_conditions[ $condition['type'] ]['metabox'];
107
  } else {
108
+ die();
109
  }
110
 
111
  if ( method_exists( $metabox[0], $metabox[1] ) ) {
112
+ call_user_func( array( $metabox[0], $metabox[1] ), $condition, $index, $form_name );
113
  }
114
 
115
  die();
116
  }
117
+
118
  /**
119
+ * Load interface for single display condition
120
  *
121
  * @since 1.7
122
  */
123
  public function load_display_condition() {
124
+
125
+ check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
126
+
127
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads' ) ) ) {
128
+ return;
129
  }
130
 
131
+ // get display condition types.
132
+ $conditions = Advanced_Ads_Display_Conditions::get_instance()->conditions;
133
+ $condition = array();
134
  $condition['type'] = isset( $_POST['type'] ) ? $_POST['type'] : '';
135
+ $index = isset( $_POST['index'] ) ? $_POST['index'] : 0;
136
 
137
  $form_name = isset( $_POST['form_name'] ) ? $_POST['form_name'] : Advanced_Ads_Display_Conditions::FORM_NAME;
138
 
139
+ if ( isset( $conditions[ $condition['type'] ] ) ) {
140
+ $metabox = $conditions[ $condition['type'] ]['metabox'];
141
  } else {
142
+ die();
143
  }
144
 
145
  if ( method_exists( $metabox[0], $metabox[1] ) ) {
146
+ call_user_func( array( $metabox[0], $metabox[1] ), $condition, $index, $form_name );
147
  }
148
 
149
  die();
150
  }
151
 
152
+ /**
153
+ * Search terms belonging to a specific taxonomy
154
+ *
155
+ * @since 1.4.7
156
+ */
157
+ public function search_terms() {
158
+
159
+ check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
160
+
161
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads' ) ) ) {
162
+ return;
163
+ }
164
+
165
+ $args = array();
166
+ $taxonomy = $_POST['tax'];
167
+ $args = array(
168
+ 'hide_empty' => false,
169
+ 'number' => 20,
170
+ );
171
+
172
+ if ( ! isset( $_POST['search'] ) || '' === $_POST['search'] ) {
173
+ die();
174
+ }
175
+
176
+ // if search is an id, search for the term id, else do a full text search.
177
+ if ( 0 !== absint( $_POST['search'] ) && strlen( $_POST['search'] ) === strlen( absint( $_POST['search'] ) ) ) {
178
+ $args['include'] = array( absint( $_POST['search'] ) );
179
+ } else {
180
+ $args['search'] = $_POST['search'];
181
+ }
182
+
183
+ $results = get_terms( $taxonomy, $args );
184
+ echo wp_json_encode( $results );
185
+ echo "\n";
186
+ die();
187
+ }
188
 
189
  /**
190
+ * Close a notice for good
191
  *
192
  * @since 1.5.3
193
  */
210
  die();
211
  }
212
 
213
+ /**
214
+ * Hide a notice for some time (7 days right now)
215
+ *
216
+ * @since 1.8.17
217
+ */
218
+ public function hide_notice() {
219
+
220
+ check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
221
+
222
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options' ) )
223
+ || empty( $_POST['notice'] )
224
  ) {
225
+ die();
226
+ }
227
 
228
+ Advanced_Ads_Admin_Notices::get_instance()->hide_notice( $_POST['notice'] );
229
+ die();
230
+ }
231
 
232
  /**
233
+ * Subscribe to newsletter
234
  *
235
  * @since 1.5.3
236
  */
240
 
241
  if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_see_interface' ) ) || empty( $_POST['notice'] )
242
  ) {
243
+ wp_send_json_error(
244
+ array(
245
+ // translators: %s is a URL.
246
+ 'message' => sprintf( __( 'An error occurred. Please use <a href="%s" target="_blank">this form</a> to sign up.', 'advanced-ads' ), 'http://eepurl.com/bk4z4P' ),
247
+ ),
248
+ 400
249
+ );
250
  }
251
 
252
+ wp_send_json_success( array( 'message' => Advanced_Ads_Admin_Notices::get_instance()->subscribe( $_POST['notice'] ) ) );
253
  }
254
 
255
  /**
256
+ * Activate license of an add-on
257
  *
258
  * @since 1.5.7
259
  */
260
+ public function activate_license() {
261
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options' ) ) ) {
262
+ return;
263
+ }
264
 
265
+ // check nonce.
266
+ check_ajax_referer( 'advads_ajax_license_nonce', 'security' );
267
 
268
+ if ( ! isset( $_POST['addon'] ) || '' === $_POST['addon'] ) {
269
+ die(); }
270
 
271
+ // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
272
+ echo Advanced_Ads_Admin_Licenses::get_instance()->activate_license( $_POST['addon'], $_POST['pluginname'], $_POST['optionslug'], $_POST['license'] );
273
+ // phpcs:enable
274
 
275
+ die();
276
  }
277
+
278
  /**
279
+ * Deactivate license of an add-on
280
  *
281
  * @since 1.6.11
282
  */
283
+ public function deactivate_license() {
284
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options' ) ) ) {
285
+ return;
286
+ }
287
 
288
+ // check nonce.
289
+ check_ajax_referer( 'advads_ajax_license_nonce', 'security' );
290
 
291
+ if ( ! isset( $_POST['addon'] ) || '' === $_POST['addon'] ) {
292
+ die(); }
293
 
294
+ // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
295
+ echo Advanced_Ads_Admin_Licenses::get_instance()->deactivate_license( $_POST['addon'], $_POST['pluginname'], $_POST['optionslug'] );
296
+ // phpcs:enable
297
 
298
+ die();
299
  }
300
 
301
  /**
302
+ * Rebuild assets for ad-blocker module
 
303
  */
304
+ public function adblock_rebuild_assets() {
305
+
306
+ check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
307
+
308
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options' ) ) ) {
309
+ return;
310
+ }
311
+
312
+ Advanced_Ads_Ad_Blocker_Admin::get_instance()->add_asset_rebuild_form();
313
+ die();
314
  }
315
 
316
  /**
317
+ * Post search (used in Display conditions)
 
318
  */
319
+ public function post_search() {
320
+
321
+ check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
322
+
323
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads' ) ) ) {
324
  return;
325
  }
326
 
329
 
330
  wp_ajax_wp_link_ajax();
331
  }
332
+
333
  /**
334
+ * Inject an ad and a placement
335
+ *
336
  * @since 1.7.3
337
  */
338
+ public function inject_placement() {
339
+
340
+ check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
341
+
342
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads' ) ) ) {
343
  die();
344
  }
345
+
346
  $ad_id = absint( $_REQUEST['ad_id'] );
347
+ if ( empty( $ad_id ) ) {
348
+ die(); }
349
+
350
+ // use existing placement.
351
  if ( isset( $_REQUEST['placement_slug'] ) ) {
352
  $xml_array[] = '<placements type="array">';
353
  $xml_array[] = '<item key="0" type="array">';
361
 
362
  Advanced_Ads_Import::get_instance()->import( $xml );
363
  if ( count( Advanced_Ads_Import::get_instance()->imported_data['placements'] ) ) {
364
+ // if the ad was assigned.
365
+ echo esc_attr( $_REQUEST['placement_slug'] );
366
  };
367
  die();
368
  }
369
 
370
+ // create new placement.
371
+ $placements = Advanced_Ads::get_instance()->get_model()->get_ad_placements_array();
372
 
373
+ $type = esc_attr( $_REQUEST['placement_type'] );
374
+
375
+ $item = 'ad_' . $ad_id;
376
+
377
+ $options = array();
378
+
379
+ // check type.
380
+ $placement_types = Advanced_Ads_Placements::get_placement_types();
381
+ if ( ! isset( $placement_types[ $type ] ) ) {
382
+ die();
383
+ }
384
+
385
+ $title = $placement_types[ $type ]['title'];
386
+
387
+ $new_placement = array(
388
+ 'type' => $type,
389
+ 'item' => $item,
390
+ 'name' => $title,
391
+ );
392
+
393
+ // set content specific options.
394
+ if ( 'post_content' === $type ) {
395
+ $index = isset( $_REQUEST['options']['index'] ) ? absint( $_REQUEST['options']['index'] ) : 1;
396
+ $new_placement['options'] = array(
397
+ 'position' => 'after',
398
+ 'index' => $index,
399
+ 'tag' => 'p',
400
+ );
401
+ }
402
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
  $slug = Advanced_Ads_Placements::save_new_placement( $new_placement );
404
+ // return potential slug.
405
+ echo esc_attr( $slug );
406
+
407
+ die();
408
  }
409
+
410
  /**
411
+ * Save ad wizard state for each user individually
412
+ *
413
  * @since 1.7.4
414
  */
415
+ public function save_wizard_state() {
416
+
417
+ check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
418
+
419
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_edit_ads' ) ) ) {
420
+ return;
421
+ }
422
+
423
+ $state = ( isset( $_REQUEST['hide_wizard'] ) && 'true' === $_REQUEST['hide_wizard'] ) ? 'true' : 'false';
424
+
425
+ // get current user.
426
+ $user_id = get_current_user_id();
427
+ if ( ! $user_id ) {
428
+ die();
429
+ }
430
+
431
+ update_user_meta( $user_id, 'advanced-ads-hide-wizard', $state );
432
+
433
  die();
 
 
 
 
 
434
  }
435
 
436
  /**
437
  * Enable Adsense Auto ads, previously "Page-Level ads"
438
  */
439
+ public function adsense_enable_pla() {
440
 
441
  check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
442
 
443
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options' ) ) ) {
444
  return;
445
  }
446
 
447
+ $options = get_option( GADSENSE_OPT_NAME, array() );
448
  $options['page-level-enabled'] = true;
449
  update_option( GADSENSE_OPT_NAME, $options );
450
+ die();
451
  }
452
+
453
  /**
454
  * Display list of Ad Health notices
455
  */
456
+ public function ad_health_notice_display() {
457
+
458
  check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
459
+
460
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options' ) ) ) {
461
  return;
462
  }
463
+
464
  Advanced_Ads_Ad_Health_Notices::get_instance()->render_widget();
465
  die();
466
  }
467
+
468
  /**
469
  * Push an Ad Health notice to the queue
470
  */
471
+ public function ad_health_notice_push() {
472
+
473
  check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
474
+
475
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options' ) ) ) {
476
  return;
477
  }
478
+
479
+ $key = ( ! empty( $_REQUEST['key'] ) ) ? esc_attr( $_REQUEST['key'] ) : false;
480
+ $attr = ( ! empty( $_REQUEST['attr'] ) && is_array( $_REQUEST['attr'] ) ) ? $_REQUEST['attr'] : array();
481
+
482
  // update or new entry?
483
+ if ( isset( $attr['mode'] ) && 'update' === $attr['mode'] ) {
484
  Advanced_Ads_Ad_Health_Notices::get_instance()->update( $key, $attr );
485
  } else {
486
  Advanced_Ads_Ad_Health_Notices::get_instance()->add( $key, $attr );
487
+ }
488
+
489
  die();
490
  }
491
+
492
  /**
493
  * Hide Ad Health notice
494
  */
495
+ public function ad_health_notice_hide() {
496
  check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
497
+
498
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options' ) ) ) {
499
  return;
500
  }
501
+
502
+ $notice_key = ( ! empty( $_REQUEST['notice'] ) ) ? esc_attr( $_REQUEST['notice'] ) : false;
503
+
504
  Advanced_Ads_Ad_Health_Notices::get_instance()->hide( $notice_key );
505
  die();
506
  }
507
+
508
  /**
509
  * Show all ignored notices of a given type
510
  */
511
+ public function ad_health_notice_unignore() {
512
  check_ajax_referer( 'advanced-ads-admin-ajax-nonce', 'nonce' );
513
+
514
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options' ) ) ) {
515
  return;
516
  }
517
+
 
 
 
518
  Advanced_Ads_Ad_Health_Notices::get_instance()->unignore();
519
  die();
520
  }
classes/ad_placements.php CHANGED
@@ -11,125 +11,159 @@
11
  */
12
 
13
  /**
14
- * grouping placements functions
15
  *
16
  * @since 1.1.0
17
  * @package Advanced_Ads_Placements
18
  * @author Thomas Maier <support@wpadvancedads.com>
19
  */
20
  class Advanced_Ads_Placements {
 
 
 
 
 
 
21
  private static $ads_for_placeholders = array();
22
- // temporarily change content during processing
 
 
 
 
23
  private static $replacements = array(
24
  'gcse:search' => 'gcse__search', // Google custom search namespaced tags.
25
  );
26
 
27
  /**
28
- * get placement types
29
  *
 
30
  * @since 1.2.1
31
- * @return arr $types array with placement types
32
  */
33
  public static function get_placement_types() {
34
  $types = array(
35
- 'default' => array(
36
- 'title' => __( 'Manual Placement', 'advanced-ads' ),
37
  'description' => __( 'Manual placement to use as function or shortcode.', 'advanced-ads' ),
38
- 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/manual.png',
39
- 'options' => array( 'show_position' => true, 'show_lazy_load' => true, 'amp' => true )
 
 
 
40
  ),
41
- 'header' => array(
42
- 'title' => __( 'Header Code', 'advanced-ads' ),
 
43
  'description' => __( 'Injected in Header (before closing &lt;/head&gt; Tag, often not visible).', 'advanced-ads' ),
44
- 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/header.png'
45
- ),
46
- 'footer' => array(
47
- 'title' => __( 'Footer Code', 'advanced-ads' ),
48
  'description' => __( 'Injected in Footer (before closing &lt;/body&gt; Tag).', 'advanced-ads' ),
49
- 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/footer.png',
50
- 'options' => array( 'amp' => true )
51
- ),
52
- 'post_top' => array(
53
- 'title' => __( 'Before Content', 'advanced-ads' ),
54
  'description' => __( 'Injected before the post content.', 'advanced-ads' ),
55
- 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/content-before.png',
56
- 'options' => array( 'show_position' => true, 'show_lazy_load' => true, 'uses_the_content' => true, 'amp' => true )
 
 
 
 
57
  ),
58
- 'post_bottom' => array(
59
- 'title' => __( 'After Content', 'advanced-ads' ),
 
60
  'description' => __( 'Injected after the post content.', 'advanced-ads' ),
61
- 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/content-after.png',
62
- 'options' => array( 'show_position' => true, 'show_lazy_load' => true, 'uses_the_content' => true, 'amp' => true )
 
 
 
 
63
  ),
64
- 'post_content' => array(
65
- 'title' => __( 'Content', 'advanced-ads' ),
 
66
  'description' => __( 'Injected into the content. You can choose the paragraph after which the ad content is displayed.', 'advanced-ads' ),
67
- 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/content-within.png',
68
- 'options' => array( 'show_position' => true, 'show_lazy_load' => true, 'uses_the_content' => true, 'amp' => true )
 
 
 
 
69
  ),
 
70
  'sidebar_widget' => array(
71
- 'title' => __( 'Sidebar Widget', 'advanced-ads' ),
72
  'description' => __( 'Create a sidebar widget with an ad. Can be placed and used like any other widget.', 'advanced-ads' ),
73
- 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/widget.png',
74
- 'options' => array( 'show_lazy_load' => true, 'amp' => true )
 
 
75
  ),
 
76
  );
 
77
  return apply_filters( 'advanced-ads-placement-types', $types );
78
  }
79
 
80
  /**
81
- * update placements if sent
82
  *
83
  * @since 1.5.2
84
- */
85
- static function update_placements(){
86
 
87
- // check user permissions
88
- if( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_placements') ) ) {
89
  return;
90
  }
91
-
92
  $success = null;
93
-
94
- // add hook of last opened placement settings to URL
95
- $hook = !empty( $_POST['advads-last-edited-placement'] ) ? '#single-placement-' . $_POST['advads-last-edited-placement'] : '';
96
 
97
- if ( isset($_POST['advads']['placement']) && check_admin_referer( 'advads-placement', 'advads_placement' ) ){
 
 
 
98
  $success = self::save_new_placement( $_POST['advads']['placement'] );
99
  }
100
- // save placement data
101
- if ( isset($_POST['advads']['placements']) && check_admin_referer( 'advads-placement', 'advads_placement' )){
102
  $success = self::save_placements( $_POST['advads']['placements'] );
103
  }
104
 
105
  $success = apply_filters( 'advanced-ads-update-placements', $success );
106
 
107
- if(isset($success)){
108
  $message = $success ? 'updated' : 'error';
109
- wp_redirect( esc_url_raw( add_query_arg(array('message' => $message) ) ) . $hook );
110
  }
111
  }
112
 
113
  /**
114
- * save a new placement
 
 
115
  *
116
- * @since 1.1.0
117
- * @param array $new_placement
118
  * @return mixed slug if saved; false if not
 
119
  */
120
- public static function save_new_placement($new_placement) {
121
- // load placements // -TODO use model
122
  $placements = Advanced_Ads::get_ad_placements_array();
123
 
124
- // create slug
125
  $new_placement['slug'] = sanitize_title( $new_placement['name'] );
126
 
127
  if ( isset( $placements[ $new_placement['slug'] ] ) ) {
128
  $i = 1;
129
- // try to save placement until we found an empty slug
130
  do {
131
  $i ++;
132
- if ( $i === 100 ) { // prevent endless loop, just in case
133
  Advanced_Ads::log( 'endless loop when injecting placement' );
134
  break;
135
  }
@@ -139,146 +173,164 @@ class Advanced_Ads_Placements {
139
  $new_placement['name'] .= ' ' . $i;
140
  }
141
 
142
- // check if slug already exists or is empty
143
- if ( $new_placement['slug'] === '' || isset( $placements[$new_placement['slug']]) || !isset( $new_placement['type'] ) ) {
144
  return false;
145
  }
146
 
147
- // make sure only allowed types are being saved
148
- $placement_types = Advanced_Ads_Placements::get_placement_types();
149
- $new_placement['type'] = (isset($placement_types[$new_placement['type']])) ? $new_placement['type'] : 'default';
150
- // escape name
151
  $new_placement['name'] = esc_attr( $new_placement['name'] );
152
 
153
- // add new place to all placements
154
- $placements[$new_placement['slug']] = array(
155
  'type' => $new_placement['type'],
156
  'name' => $new_placement['name'],
157
- 'item' => $new_placement['item']
158
  );
159
-
160
- // add index options
161
- if ( isset($new_placement['options']) ){
162
- $placements[$new_placement['slug']]['options'] = $new_placement['options'];
163
- if ( isset($placements[$new_placement['slug']]['options']['index']) ) {
164
- $placements[$new_placement['slug']]['options']['index'] = absint( $placements[$new_placement['slug']]['options']['index'] ); }
165
- }
166
-
167
- // save array
 
168
  Advanced_Ads::get_instance()->get_model()->update_ad_placements_array( $placements );
169
 
170
  return $new_placement['slug'];
171
  }
172
 
173
  /**
174
- * save placements
 
 
175
  *
176
- * @since 1.1.0
177
- * @param array $placement_items
178
  * @return mixed true if saved; error message if not
 
179
  */
180
- public static function save_placements($placement_items) {
181
 
182
- // load placements // -TODO use model
183
  $placements = Advanced_Ads::get_ad_placements_array();
184
 
185
  foreach ( $placement_items as $_placement_slug => $_placement ) {
186
- // remove the placement
187
- if ( isset($_placement['delete']) ) {
188
- unset($placements[$_placement_slug]);
189
  continue;
190
  }
191
- // save item
192
- if ( isset($_placement['item']) ) {
193
- $placements[$_placement_slug]['item'] = $_placement['item']; }
194
- // save item options
195
- if ( isset($_placement['options']) ){
196
- $placements[$_placement_slug]['options'] = $_placement['options'];
197
- if ( isset($placements[$_placement_slug]['options']['index']) ) {
198
- $placements[$_placement_slug]['options']['index'] = absint( $placements[$_placement_slug]['options']['index'] ); }
 
 
199
  } else {
200
- $placements[$_placement_slug]['options'] = array();
201
  }
202
  }
203
 
204
- // save array
205
  Advanced_Ads::get_instance()->get_model()->update_ad_placements_array( $placements );
206
 
207
  return true;
208
  }
209
 
210
  /**
211
- * get items for item select field
212
  *
 
213
  * @since 1.1
214
- * @return arr $select items for select field
215
  */
216
  public static function items_for_select() {
217
  $select = array();
218
- $model = Advanced_Ads::get_instance()->get_model();
219
 
220
- // load all ad groups
221
  $groups = $model->get_ad_groups();
222
  foreach ( $groups as $_group ) {
223
- $select['groups']['group_' . $_group->term_id] = $_group->name;
224
  }
225
 
226
- // load all ads
227
- $ads = $model->get_ads( array('orderby' => 'title', 'order' => 'ASC') );
 
 
 
 
 
228
  foreach ( $ads as $_ad ) {
229
- $select['ads']['ad_' . $_ad->ID] = $_ad->post_title;
230
  }
231
 
232
  return $select;
233
  }
234
 
235
  /**
236
- * get html tags for content injection
237
  *
 
238
  * @since 1.3.5
239
- * @return arr $tags array with tags that can be used for content injection
240
  */
241
- public static function tags_for_content_injection(){
242
- $tags = apply_filters( 'advanced-ads-tags-for-injection', array(
243
- 'p' => sprintf( __( 'paragraph (%s)', 'advanced-ads' ), '&lt;p&gt;' ),
244
- 'pwithoutimg' => sprintf( __( 'paragraph without image (%s)', 'advanced-ads' ), '&lt;p&gt;' ),
245
- 'h2' => sprintf( __( 'headline 2 (%s)', 'advanced-ads' ), '&lt;h2&gt;' ),
246
- 'h3' => sprintf( __( 'headline 3 (%s)', 'advanced-ads' ), '&lt;h3&gt;' ),
247
- 'h4' => sprintf( __( 'headline 4 (%s)', 'advanced-ads' ), '&lt;h4&gt;' ),
248
- ));
 
 
 
 
 
 
 
 
249
 
250
  return $tags;
251
  }
252
 
253
  /**
254
- * return content of a placement
255
  *
256
- * @since 1.1.0
257
- * @param string $id slug of the display
258
- * @param array $args optional arguments (passed to child)
 
259
  */
260
  public static function output( $id = '', $args = array() ) {
261
- // get placement data for the slug
262
- if ( $id == '' ) {
263
  return;
264
  }
265
 
266
  $placements = Advanced_Ads::get_ad_placements_array();
267
- $placement = ( isset( $placements[ $id ] ) && is_array( $placements[ $id ] ) ) ? $placements[ $id ] : array();
268
 
269
  if ( isset( $args['change-placement'] ) ) {
270
- // some options was provided by the user
271
- $placement = Advanced_Ads_Utils::merge_deep_array( array( $placement, $args['change-placement'] ) ) ;
272
  }
273
 
274
- if ( isset( $placement['item'] ) && $placement['item'] !== '' ) {
275
  $_item = explode( '_', $placement['item'] );
276
 
277
  if ( ! isset( $_item[1] ) || empty( $_item[1] ) ) {
278
- return ;
279
  }
280
 
281
- // inject options
282
  if ( isset( $placement['options'] ) && is_array( $placement['options'] ) ) {
283
  foreach ( $placement['options'] as $_k => $_v ) {
284
  if ( ! isset( $args[ $_k ] ) ) {
@@ -287,20 +339,20 @@ class Advanced_Ads_Placements {
287
  }
288
  }
289
 
290
- // inject placement type
291
  if ( isset( $placement['type'] ) ) {
292
- $args[ 'placement_type' ] = $placement['type'];
293
  }
294
 
295
- // options
296
  $prefix = Advanced_Ads_Plugin::get_instance()->get_frontend_prefix();
297
 
298
- // return either ad or group content
299
  switch ( $_item[0] ) {
300
  case 'ad':
301
- case Advanced_Ads_Select::AD :
302
- // create class from placement id (not if header injection)
303
- if ( ! isset( $placement['type'] ) || $placement['type'] !== 'header' ) {
304
  if ( ! isset( $args['output'] ) ) {
305
  $args['output'] = array();
306
  }
@@ -313,203 +365,212 @@ class Advanced_Ads_Placements {
313
  }
314
  }
315
 
316
- // fix method id
317
  $_item[0] = Advanced_Ads_Select::AD;
318
  break;
319
 
320
- // avoid loops (programmatical error)
321
- case Advanced_Ads_Select::PLACEMENT :
322
  return;
323
 
324
- case Advanced_Ads_Select::GROUP :
325
- $class = $prefix . $id;
326
- if ( ( isset( $placement['type'] ) && $placement['type'] !== 'header' )
327
- && ( !isset( $args['output']['class'] )
328
- || !is_array( $args['output']['class'] )
329
- || !in_array( $class, $args['output']['class'] ) ) ) {
330
- $args['output']['class'][] = $class;
331
- }
332
  default:
333
  }
334
 
335
- // create placement id for various features
336
  $args['output']['placement_id'] = $id;
337
 
338
- // add the placement to the global output array
339
  $advads = Advanced_Ads::get_instance();
340
- $name = isset( $placement['name'] ) ? $placement['name'] : $id;
341
 
342
  if ( ! isset( $args['global_output'] ) || $args['global_output'] ) {
343
- $advads->current_ads[] = array( 'type' => 'placement', 'id' => $id, 'title' => $name );
 
 
 
 
344
  }
345
 
346
  $result = Advanced_Ads_Select::get_instance()->get_ad_by_method( (int) $_item[1], $_item[0], $args );
347
 
348
  return $result;
349
  }
 
 
350
  }
351
 
352
  /**
353
- * inject ads directly into the content
354
  *
 
 
 
 
 
355
  * @since 1.2.1
356
- * @param string $placement_id id of the placement
357
- * @param arr $placement_opts placement options
358
- * @param string $content
359
- * @return type
360
- * @link inspired by http://www.wpbeginner.com/wp-tutorials/how-to-insert-ads-within-your-post-content-in-wordpress/
361
  */
362
- public static function &inject_in_content($placement_id, $placement_opts, &$content) {
363
  if ( ! extension_loaded( 'dom' ) ) {
364
  return $content;
365
  }
366
 
367
- // get plugin options
368
  $plugin_options = Advanced_Ads::get_instance()->options();
369
-
370
- $wpCharset = get_bloginfo('charset');
371
- // parse document as DOM (fragment - having only a part of an actual post given)
372
 
373
- $content_to_load = self::get_content_to_load( $content, $wpCharset );
 
 
 
374
  if ( ! $content_to_load ) {
375
  return $content;
376
  }
377
 
378
- $dom = new DOMDocument('1.0', $wpCharset);
379
- // may loose some fragments or add autop-like code
380
- libxml_use_internal_errors(true); // avoid notices and warnings - html is most likely malformed
381
 
382
- $success = $dom->loadHtml('<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=' . $wpCharset . '" /><body>' . $content_to_load);
383
- libxml_use_internal_errors(false);
384
- if ($success !== true) {
385
  // -TODO handle cases were dom-parsing failed (at least inform user)
386
  return $content;
387
  }
388
 
389
- // parse arguments
390
- $tag = isset($placement_opts['tag']) ? $placement_opts['tag'] : 'p';
391
- $tag = preg_replace('/[^a-z0-9]/i', '', $tag); // simplify tag
392
 
393
- // allow more complex xPath expression
394
  $tag = apply_filters( 'advanced-ads-placement-content-injection-xpath', $tag, $placement_opts );
395
 
396
- if ( $tag === 'pwithoutimg' ) {
397
  $tag = 'p[not(descendant::img)]';
398
  }
399
 
400
- // select positions
401
- $xpath = new DOMXPath($dom);
402
- $items = $xpath->query('/html/body/' . $tag);
403
 
404
  $options = array(
405
- 'allowEmpty' => false, // whether the tag can be empty to be counted
406
- 'paragraph_select_from_bottom' => isset($placement_opts['start_from_bottom']) && $placement_opts['start_from_bottom'],
407
- // only has before and after
408
- 'before' => isset($placement_opts['position']) && $placement_opts['position'] === 'before'
409
  );
410
 
411
- $options['paragraph_id'] = isset($placement_opts['index']) ? $placement_opts['index'] : 1;
412
  $options['paragraph_id'] = max( 1, (int) $options['paragraph_id'] );
413
 
414
- // if there are too few items at this level test nesting
415
- $options['itemLimit'] = $tag === 'p' ? 2 : 1;
416
 
417
- // trigger such a high item limit that all elements will be considered
418
- if( ! empty($plugin_options['content-injection-level-disabled'] ) ){
419
  $options['itemLimit'] = 1000;
420
  }
421
-
422
- // allow hooks to change some options
423
  $options = apply_filters(
424
  'advanced-ads-placement-content-injection-options',
425
  $options,
426
- $tag );
 
427
 
428
- if ($items->length < $options['itemLimit'] ) {
429
- $items = $xpath->query('/html/body/*/' . $tag);
430
  }
431
- // try third level
432
  if ( $items->length < $options['itemLimit'] ) {
433
- $items = $xpath->query('/html/body/*/*/' . $tag);
434
  }
435
- // try all levels as last resort
436
  if ( $items->length < $options['itemLimit'] ) {
437
  $items = $xpath->query( '//' . $tag );
438
  }
439
-
440
- // allow to select other elements
441
  $items = apply_filters( 'advanced-ads-placement-content-injection-items', $items, $xpath, $tag );
442
 
443
- // filter empty tags from items
444
- $whitespaces = json_decode('"\t\n\r \u00A0"');
445
- $paragraphs = array();
446
- foreach ($items as $item) {
447
- if ( $options['allowEmpty'] || ( isset($item->textContent) && trim($item->textContent, $whitespaces) !== '' ) ) {
448
  $paragraphs[] = $item;
449
  }
450
  }
451
 
452
- $options['paragraph_count'] = count($paragraphs);
 
 
 
453
 
454
- if ($options['paragraph_count'] >= $options['paragraph_id']) {
455
- $offset = $options['paragraph_select_from_bottom'] ? $options['paragraph_count'] - $options['paragraph_id'] : $options['paragraph_id'] - 1;
456
- $offsets = apply_filters( 'advanced-ads-placement-content-offsets', array( $offset ), $options, $placement_opts );
457
  $did_inject = false;
458
 
459
  foreach ( $offsets as $offset ) {
460
- // inject
461
- $node = apply_filters( 'advanced-ads-placement-content-injection-node', $paragraphs[$offset], $tag, $options['before'] );
462
 
463
- $adContent = Advanced_Ads_Select::get_instance()->get_ad_by_method( $placement_id, 'placement', $placement_opts );
464
 
465
- if ( trim( $adContent, $whitespaces ) === '' ) {
466
  continue;
467
  }
468
 
469
- $adContent = self::filter_ad_content( $adContent, $node->tagName, $options );
 
470
 
471
  // convert HTML to XML!
472
- $adDom = new DOMDocument('1.0', $wpCharset);
473
- libxml_use_internal_errors(true);
474
- $adDom->loadHtml('<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=' . $wpCharset . '" /><body>' . $adContent);
475
- // log errors
476
- if ( defined ( 'WP_DEBUG' ) && WP_DEBUG && current_user_can( 'advanced_ads_manage_options' ) ) {
477
- foreach( libxml_get_errors() as $_error ) {
478
- // continue, if there is '&' symbol, but not HTML entity
479
  if ( false === stripos( $_error->message, 'htmlParseEntityRef:' ) ) {
480
  Advanced_Ads::log( 'possible content injection error for placement "' . $placement_id . '": ' . print_r( $_error, true ) );
481
  }
482
  }
483
  }
484
 
485
-
486
  if ( $options['before'] ) {
487
- $refNode = $node;
488
 
489
- foreach ( $adDom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
490
  $importedNode = $dom->importNode( $importedNode, true );
491
- $refNode->parentNode->insertBefore( $importedNode, $refNode );
492
  }
493
  } else {
494
- // append before next node or as last child to body
495
- $refNode = $node->nextSibling;
496
- if (isset($refNode)) {
497
 
498
- foreach ( $adDom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
499
  $importedNode = $dom->importNode( $importedNode, true );
500
- $refNode->parentNode->insertBefore( $importedNode, $refNode );
501
  }
502
-
503
  } else {
504
- // append to body; -TODO using here that we only select direct children of the body tag
505
- foreach ( $adDom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
506
  $importedNode = $dom->importNode( $importedNode, true );
507
  $node->parentNode->appendChild( $importedNode );
508
  }
509
  }
510
  }
511
 
512
- libxml_use_internal_errors(false);
513
  $did_inject = true;
514
  }
515
 
@@ -518,25 +579,33 @@ class Advanced_Ads_Placements {
518
  }
519
 
520
  $content_orig = $content;
521
- // convert to text-representation
522
  $content = $dom->saveHTML();
523
  $content = self::prepare_output( $content, $content_orig );
524
 
525
- /**
526
- * show a warning to ad admins in the Ad Health bar in the frontend, when
527
- *
528
- * * the level limitation was not disabled
529
- * * could not inject one ad (as by use of `elseif` here)
530
- * * but there are enough elements on the site, but just in sub-containers
531
- *
532
- */
533
- } elseif( current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options') )
534
- && empty($plugin_options['content-injection-level-disabled'] ) ) {
535
-
536
- // check if there are more elements without limitation
537
  $all_items = $xpath->query( '//' . $tag );
538
- if( $options['paragraph_id'] <= $all_items->length ){
539
- // add a warning to ad health
 
 
 
 
 
 
 
 
 
540
  add_filter( 'advanced-ads-ad-health-nodes', array( 'Advanced_Ads_Placements', 'add_ad_health_node' ) );
541
  }
542
  }
@@ -548,17 +617,18 @@ class Advanced_Ads_Placements {
548
  * Get content to load.
549
  *
550
  * @param string $content Original content.
551
- * @param $wpCharset Blog charset.
 
552
  * @return string $content Content to load.
553
  */
554
- private static function get_content_to_load( $content, $wpCharset ) {
555
  $plugin_options = Advanced_Ads::get_instance()->options();
556
 
557
  // Prevent removing closing tags in scripts.
558
- $content_to_load= preg_replace( '/<script.*?<\/script>/', '<!--\0-->', $content);
559
 
560
- // check which priority the wpautop filter has; might have been disabled on purpose
561
- $wpautop_priority = has_filter( 'the_content', 'wpautop');
562
  if ( $wpautop_priority && Advanced_Ads_Plugin::get_instance()->get_content_injection_priority() < $wpautop_priority ) {
563
  $content_to_load = wpautop( $content_to_load );
564
  }
@@ -569,23 +639,24 @@ class Advanced_Ads_Placements {
569
  /**
570
  * Filter ad content.
571
  *
572
- * @param string $adContent Ad content.
573
  * @param string $tag_name tar before/after the content.
574
- * @param array $options Injection options.
575
- * @return string $adContent Ad content.
 
576
  */
577
  private static function filter_ad_content( $ad_content, $tag_name, $options ) {
578
  $plugin_options = Advanced_Ads::get_instance()->options();
579
 
580
- //Inject placeholder.
581
- $id = count( self::$ads_for_placeholders );
582
  self::$ads_for_placeholders[] = array(
583
- 'id' => $id,
584
- 'tag' => $tag_name,
585
  'type' => $options['before'] ? 'before' : 'after',
586
- 'ad' => $ad_content
587
  );
588
- $ad_content = '%advads_placeholder_' . $id . '%';
589
 
590
  return $ad_content;
591
  }
@@ -595,12 +666,13 @@ class Advanced_Ads_Placements {
595
  *
596
  * @param string $content Modified content.
597
  * @param string $content_orig Original content.
 
598
  * @return string $content Content to output.
599
  */
600
  private static function prepare_output( $content, $content_orig ) {
601
  $plugin_options = Advanced_Ads::get_instance()->options();
602
 
603
- $content = self::inject_ads( $content, $content_orig, self::$ads_for_placeholders );
604
  self::$ads_for_placeholders = array();
605
 
606
  return $content;
@@ -610,53 +682,71 @@ class Advanced_Ads_Placements {
610
  * Search for ad placeholders in the `$content` to determine positions at which to inject ads.
611
  * Given the positions, inject ads into `$content_orig.
612
  *
613
- * @param string $content Post content with injected ad placeholders.
614
  * @param string $content_orig Unmodified post content.
615
- * @param arrray $options Injection options.
616
- * @param arrray $ads_for_placeholders Array of ads.
617
- * Each ad contains placeholder id, before or after which tag to inject the ad, the ad content.
 
618
  * @return string $content
619
  */
620
  private static function inject_ads( $content, $content_orig, $ads_for_placeholders ) {
621
- $self_closing_tags = array( 'area', 'base', 'basefont', 'bgsound', 'br', 'col', 'embed', 'frame', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
622
 
623
  // It is not possible to append/prepend in self closing tags.
624
  foreach ( $ads_for_placeholders as &$ad_content ) {
625
  if ( ( 'prepend' === $ad_content['type'] || 'append' === $ad_content['type'] )
626
- && in_array( $ad_content['tag'], $self_closing_tags, true ) ) {
627
  $ad_content['type'] = 'after';
628
  }
629
  }
630
  unset( $ad_content );
631
  usort( $ads_for_placeholders, array( 'Advanced_Ads_Placements', 'sort_ads_for_placehoders' ) );
632
 
633
-
634
  // Add tags before/after which ad placehoders were injected.
635
  foreach ( $ads_for_placeholders as $ad_content ) {
636
  $tag = $ad_content['tag'];
637
 
638
- switch( $ad_content['type'] ) {
639
- case 'before':
640
- case 'prepend':
641
- $alts[] = "<${tag}[^>]*>";
642
- break;
643
- case 'after':
644
- if ( in_array( $tag, $self_closing_tags, true ) ) {
645
  $alts[] = "<${tag}[^>]*>";
646
- } else {
 
 
 
 
 
 
 
 
647
  $alts[] = "</${tag}>";
648
- }
649
- break;
650
- case 'append':
651
- $alts[] = "</${tag}>";
652
- break;
653
  }
654
-
655
  }
656
- $alts = array_unique( $alts );
657
  $tag_regexp = implode( '|', $alts );
658
  // Add ad placeholder.
659
- $alts[] = '%advads_placeholder_(?:\d+)%';
660
  $tag_and_placeholder_regexp = implode( '|', $alts );
661
 
662
  preg_match_all( "#{$tag_and_placeholder_regexp}#i", $content, $tag_matches );
@@ -665,7 +755,7 @@ class Advanced_Ads_Placements {
665
  // For each tag located before/after an ad placeholder, find its offset among the same tags.
666
  foreach ( $tag_matches[0] as $r ) {
667
  if ( preg_match( '/%advads_placeholder_(\d+)%/', $r, $result ) ) {
668
- $id = $result[1];
669
  $found_ad = false;
670
  foreach ( $ads_for_placeholders as $n => $ad ) {
671
  if ( (int) $ad['id'] === (int) $id ) {
@@ -677,26 +767,25 @@ class Advanced_Ads_Placements {
677
  continue;
678
  }
679
 
680
- switch( $found_ad['type'] ) {
681
  case 'before':
682
  case 'append':
683
  $ads_for_placeholders[ $n ]['offset'] = $count;
684
  break;
685
  case 'after':
686
  case 'prepend':
687
- $ads_for_placeholders[ $n ]['offset'] = $count -1;
688
  break;
689
  }
690
-
691
  } else {
692
- $count++;
693
  }
694
  }
695
 
696
  // Find tags before/after which we need to inject ads.
697
  preg_match_all( "#{$tag_regexp}#i", $content_orig, $orig_tag_matches, PREG_OFFSET_CAPTURE );
698
  $new_content = '';
699
- $pos = 0;
700
 
701
  foreach ( $orig_tag_matches[0] as $n => $r ) {
702
  $to_inject = array();
@@ -708,23 +797,24 @@ class Advanced_Ads_Placements {
708
  }
709
 
710
  foreach ( $to_inject as $item ) {
711
- switch( $item['type'] ) {
712
- case 'before':
713
- case 'append':
714
- $found_pos = $r[1];
715
- break;
716
- case 'after':
717
- case 'prepend':
718
- $found_pos = $r[1] + strlen( $r[0] );
719
- break;
720
  }
721
 
722
  $new_content .= substr( $content_orig, $pos, $found_pos - $pos );
723
- $pos = $found_pos;
724
  $new_content .= $item['ad'];
725
  }
726
  }
727
  $new_content .= substr( $content_orig, $pos );
 
728
  return $new_content;
729
  }
730
 
@@ -732,8 +822,9 @@ class Advanced_Ads_Placements {
732
  /**
733
  * Callback function for usort() to sort ads for placeholders.
734
  *
735
- * @param array $first The first array to compare.
736
  * @param array $second The second array to compare.
 
737
  * @return int 0 if both objects equal. -1 if second array should come first, 1 otherwise.
738
  */
739
  public static function sort_ads_for_placehoders( $first, $second ) {
@@ -741,43 +832,55 @@ class Advanced_Ads_Placements {
741
  return 0;
742
  }
743
 
744
- $num = array( 'before' => 1, 'prepend' => 2, 'append' => 3, 'after' => 4 );
 
 
 
 
 
745
 
746
- return $num[ $first['type'] ] > $num[ $second['type'] ] ? 1 : -1;
747
  }
748
 
749
  /**
750
  * Add a warning to 'Ad health'.
751
  *
752
- * @param array $nodes.
 
753
  * @return array $nodes.
754
  */
755
  public static function add_ad_health_node( $nodes ) {
756
- $nodes[] = array( 'type' => 1, 'data' => array(
757
- 'parent' => 'advanced_ads_ad_health',
758
- 'id' => 'advanced_ads_ad_health_the_content_not_enough_elements',
759
- 'title' => sprintf(
 
 
760
  /* translators: %s stands for the name of the "Disable level limitation" option and automatically translated as well */
761
- __( 'Set <em>%s</em> to show more ads', 'advanced-ads' ),
762
- __('Disable level limitation', 'advanced-ads' ) ),
763
- 'href' => admin_url( '/admin.php?page=advanced-ads-settings#top#general' ),
764
- 'meta' => array(
765
- 'class' => 'advanced_ads_ad_health_warning',
766
- 'target' => '_blank'
767
- )
768
- ) );
 
 
 
769
  return $nodes;
770
  }
771
 
772
  /**
773
- * check if the placement can be displayed
 
 
774
  *
775
- * @since 1.6.9
776
- * @param int $id placement id
777
  * @return bool true if placement can be displayed
 
778
  */
779
- static function can_display( $id = 0 ){
780
- if ( ! isset($id) || $id === 0 ) {
781
  return true;
782
  }
783
 
@@ -788,7 +891,8 @@ class Advanced_Ads_Placements {
788
  * Get the placements that includes the ad or group.
789
  *
790
  * @param string $type 'ad' or 'group'.
791
- * @param int $id Id.
 
792
  * @return array
793
  */
794
  public static function get_placements_by( $type, $id ) {
@@ -804,5 +908,136 @@ class Advanced_Ads_Placements {
804
  return $result;
805
  }
806
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
807
  }
808
 
11
  */
12
 
13
  /**
14
+ * Grouping placements functions
15
  *
16
  * @since 1.1.0
17
  * @package Advanced_Ads_Placements
18
  * @author Thomas Maier <support@wpadvancedads.com>
19
  */
20
  class Advanced_Ads_Placements {
21
+
22
+ /**
23
+ * Gather placeholders which later are replaced by the ads
24
+ *
25
+ * @var array $ads_for_placeholders
26
+ */
27
  private static $ads_for_placeholders = array();
28
+ /**
29
+ * Temporarily change content during processing
30
+ *
31
+ * @var array $placements
32
+ */
33
  private static $replacements = array(
34
  'gcse:search' => 'gcse__search', // Google custom search namespaced tags.
35
  );
36
 
37
  /**
38
+ * Get placement types
39
  *
40
+ * @return array $types array with placement types
41
  * @since 1.2.1
 
42
  */
43
  public static function get_placement_types() {
44
  $types = array(
45
+ 'default' => array(
46
+ 'title' => __( 'Manual Placement', 'advanced-ads' ),
47
  'description' => __( 'Manual placement to use as function or shortcode.', 'advanced-ads' ),
48
+ 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/manual.png',
49
+ 'options' => array(
50
+ 'show_position' => true,
51
+ 'show_lazy_load' => true,
52
+ 'amp' => true,
53
  ),
54
+ ),
55
+ 'header' => array(
56
+ 'title' => __( 'Header Code', 'advanced-ads' ),
57
  'description' => __( 'Injected in Header (before closing &lt;/head&gt; Tag, often not visible).', 'advanced-ads' ),
58
+ 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/header.png',
59
+ ),
60
+ 'footer' => array(
61
+ 'title' => __( 'Footer Code', 'advanced-ads' ),
62
  'description' => __( 'Injected in Footer (before closing &lt;/body&gt; Tag).', 'advanced-ads' ),
63
+ 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/footer.png',
64
+ 'options' => array( 'amp' => true ),
65
+ ),
66
+ 'post_top' => array(
67
+ 'title' => __( 'Before Content', 'advanced-ads' ),
68
  'description' => __( 'Injected before the post content.', 'advanced-ads' ),
69
+ 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/content-before.png',
70
+ 'options' => array(
71
+ 'show_position' => true,
72
+ 'show_lazy_load' => true,
73
+ 'uses_the_content' => true,
74
+ 'amp' => true,
75
  ),
76
+ ),
77
+ 'post_bottom' => array(
78
+ 'title' => __( 'After Content', 'advanced-ads' ),
79
  'description' => __( 'Injected after the post content.', 'advanced-ads' ),
80
+ 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/content-after.png',
81
+ 'options' => array(
82
+ 'show_position' => true,
83
+ 'show_lazy_load' => true,
84
+ 'uses_the_content' => true,
85
+ 'amp' => true,
86
  ),
87
+ ),
88
+ 'post_content' => array(
89
+ 'title' => __( 'Content', 'advanced-ads' ),
90
  'description' => __( 'Injected into the content. You can choose the paragraph after which the ad content is displayed.', 'advanced-ads' ),
91
+ 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/content-within.png',
92
+ 'options' => array(
93
+ 'show_position' => true,
94
+ 'show_lazy_load' => true,
95
+ 'uses_the_content' => true,
96
+ 'amp' => true,
97
  ),
98
+ ),
99
  'sidebar_widget' => array(
100
+ 'title' => __( 'Sidebar Widget', 'advanced-ads' ),
101
  'description' => __( 'Create a sidebar widget with an ad. Can be placed and used like any other widget.', 'advanced-ads' ),
102
+ 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/widget.png',
103
+ 'options' => array(
104
+ 'show_lazy_load' => true,
105
+ 'amp' => true,
106
  ),
107
+ ),
108
  );
109
+
110
  return apply_filters( 'advanced-ads-placement-types', $types );
111
  }
112
 
113
  /**
114
+ * Update placements if sent
115
  *
116
  * @since 1.5.2
117
+ */
118
+ public static function update_placements() {
119
 
120
+ // check user permissions.
121
+ if ( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_placements' ) ) ) {
122
  return;
123
  }
124
+
125
  $success = null;
 
 
 
126
 
127
+ // add hook of last opened placement settings to URL.
128
+ $hook = ! empty( $_POST['advads-last-edited-placement'] ) ? '#single-placement-' . $_POST['advads-last-edited-placement'] : '';
129
+
130
+ if ( isset( $_POST['advads']['placement'] ) && check_admin_referer( 'advads-placement', 'advads_placement' ) ) {
131
  $success = self::save_new_placement( $_POST['advads']['placement'] );
132
  }
133
+ // save placement data.
134
+ if ( isset( $_POST['advads']['placements'] ) && check_admin_referer( 'advads-placement', 'advads_placement' ) ) {
135
  $success = self::save_placements( $_POST['advads']['placements'] );
136
  }
137
 
138
  $success = apply_filters( 'advanced-ads-update-placements', $success );
139
 
140
+ if ( isset( $success ) ) {
141
  $message = $success ? 'updated' : 'error';
142
+ wp_redirect( esc_url_raw( add_query_arg( array( 'message' => $message ) ) ) . $hook );
143
  }
144
  }
145
 
146
  /**
147
+ * Save a new placement
148
+ *
149
+ * @param array $new_placement information about the new placement.
150
  *
 
 
151
  * @return mixed slug if saved; false if not
152
+ * @since 1.1.0
153
  */
154
+ public static function save_new_placement( $new_placement ) {
155
+ // load placements // -TODO use model.
156
  $placements = Advanced_Ads::get_ad_placements_array();
157
 
158
+ // create slug.
159
  $new_placement['slug'] = sanitize_title( $new_placement['name'] );
160
 
161
  if ( isset( $placements[ $new_placement['slug'] ] ) ) {
162
  $i = 1;
163
+ // try to save placement until we found an empty slug.
164
  do {
165
  $i ++;
166
+ if ( 100 === $i ) { // prevent endless loop, just in case.
167
  Advanced_Ads::log( 'endless loop when injecting placement' );
168
  break;
169
  }
173
  $new_placement['name'] .= ' ' . $i;
174
  }
175
 
176
+ // check if slug already exists or is empty.
177
+ if ( '' === $new_placement['slug'] || isset( $placements[ $new_placement['slug'] ] ) || ! isset( $new_placement['type'] ) ) {
178
  return false;
179
  }
180
 
181
+ // make sure only allowed types are being saved.
182
+ $placement_types = self::get_placement_types();
183
+ $new_placement['type'] = ( isset( $placement_types[ $new_placement['type'] ] ) ) ? $new_placement['type'] : 'default';
184
+ // escape name.
185
  $new_placement['name'] = esc_attr( $new_placement['name'] );
186
 
187
+ // add new place to all placements.
188
+ $placements[ $new_placement['slug'] ] = array(
189
  'type' => $new_placement['type'],
190
  'name' => $new_placement['name'],
191
+ 'item' => $new_placement['item'],
192
  );
193
+
194
+ // add index options.
195
+ if ( isset( $new_placement['options'] ) ) {
196
+ $placements[ $new_placement['slug'] ]['options'] = $new_placement['options'];
197
+ if ( isset( $placements[ $new_placement['slug'] ]['options']['index'] ) ) {
198
+ $placements[ $new_placement['slug'] ]['options']['index'] = absint( $placements[ $new_placement['slug'] ]['options']['index'] );
199
+ }
200
+ }
201
+
202
+ // save array.
203
  Advanced_Ads::get_instance()->get_model()->update_ad_placements_array( $placements );
204
 
205
  return $new_placement['slug'];
206
  }
207
 
208
  /**
209
+ * Save placements
210
+ *
211
+ * @param array $placement_items placements.
212
  *
 
 
213
  * @return mixed true if saved; error message if not
214
+ * @since 1.1.0
215
  */
216
+ public static function save_placements( $placement_items ) {
217
 
218
+ // load placements // -TODO use model.
219
  $placements = Advanced_Ads::get_ad_placements_array();
220
 
221
  foreach ( $placement_items as $_placement_slug => $_placement ) {
222
+ // remove the placement.
223
+ if ( isset( $_placement['delete'] ) ) {
224
+ unset( $placements[ $_placement_slug ] );
225
  continue;
226
  }
227
+ // save item.
228
+ if ( isset( $_placement['item'] ) ) {
229
+ $placements[ $_placement_slug ]['item'] = $_placement['item'];
230
+ }
231
+ // save item options.
232
+ if ( isset( $_placement['options'] ) ) {
233
+ $placements[ $_placement_slug ]['options'] = $_placement['options'];
234
+ if ( isset( $placements[ $_placement_slug ]['options']['index'] ) ) {
235
+ $placements[ $_placement_slug ]['options']['index'] = absint( $placements[ $_placement_slug ]['options']['index'] );
236
+ }
237
  } else {
238
+ $placements[ $_placement_slug ]['options'] = array();
239
  }
240
  }
241
 
242
+ // save array.
243
  Advanced_Ads::get_instance()->get_model()->update_ad_placements_array( $placements );
244
 
245
  return true;
246
  }
247
 
248
  /**
249
+ * Get items for item select field
250
  *
251
+ * @return array $select items for select field
252
  * @since 1.1
 
253
  */
254
  public static function items_for_select() {
255
  $select = array();
256
+ $model = Advanced_Ads::get_instance()->get_model();
257
 
258
+ // load all ad groups.
259
  $groups = $model->get_ad_groups();
260
  foreach ( $groups as $_group ) {
261
+ $select['groups'][ 'group_' . $_group->term_id ] = $_group->name;
262
  }
263
 
264
+ // load all ads.
265
+ $ads = $model->get_ads(
266
+ array(
267
+ 'orderby' => 'title',
268
+ 'order' => 'ASC',
269
+ )
270
+ );
271
  foreach ( $ads as $_ad ) {
272
+ $select['ads'][ 'ad_' . $_ad->ID ] = $_ad->post_title;
273
  }
274
 
275
  return $select;
276
  }
277
 
278
  /**
279
+ * Get html tags for content injection
280
  *
281
+ * @return array $tags array with tags that can be used for content injection
282
  * @since 1.3.5
 
283
  */
284
+ public static function tags_for_content_injection() {
285
+ $tags = apply_filters(
286
+ 'advanced-ads-tags-for-injection',
287
+ array(
288
+ // translators: %s is an html tag.
289
+ 'p' => sprintf( __( 'paragraph (%s)', 'advanced-ads' ), '&lt;p&gt;' ),
290
+ // translators: %s is an html tag.
291
+ 'pwithoutimg' => sprintf( __( 'paragraph without image (%s)', 'advanced-ads' ), '&lt;p&gt;' ),
292
+ // translators: %s is an html tag.
293
+ 'h2' => sprintf( __( 'headline 2 (%s)', 'advanced-ads' ), '&lt;h2&gt;' ),
294
+ // translators: %s is an html tag.
295
+ 'h3' => sprintf( __( 'headline 3 (%s)', 'advanced-ads' ), '&lt;h3&gt;' ),
296
+ // translators: %s is an html tag.
297
+ 'h4' => sprintf( __( 'headline 4 (%s)', 'advanced-ads' ), '&lt;h4&gt;' ),
298
+ )
299
+ );
300
 
301
  return $tags;
302
  }
303
 
304
  /**
305
+ * Return content of a placement
306
  *
307
+ * @param string $id slug of the display.
308
+ * @param array $args optional arguments (passed to child).
309
+ *
310
+ * @return string
311
  */
312
  public static function output( $id = '', $args = array() ) {
313
+ // get placement data for the slug.
314
+ if ( '' == $id ) {
315
  return;
316
  }
317
 
318
  $placements = Advanced_Ads::get_ad_placements_array();
319
+ $placement = ( isset( $placements[ $id ] ) && is_array( $placements[ $id ] ) ) ? $placements[ $id ] : array();
320
 
321
  if ( isset( $args['change-placement'] ) ) {
322
+ // some options was provided by the user.
323
+ $placement = Advanced_Ads_Utils::merge_deep_array( array( $placement, $args['change-placement'] ) );
324
  }
325
 
326
+ if ( isset( $placement['item'] ) && '' !== $placement['item'] ) {
327
  $_item = explode( '_', $placement['item'] );
328
 
329
  if ( ! isset( $_item[1] ) || empty( $_item[1] ) ) {
330
+ return;
331
  }
332
 
333
+ // inject options.
334
  if ( isset( $placement['options'] ) && is_array( $placement['options'] ) ) {
335
  foreach ( $placement['options'] as $_k => $_v ) {
336
  if ( ! isset( $args[ $_k ] ) ) {
339
  }
340
  }
341
 
342
+ // inject placement type.
343
  if ( isset( $placement['type'] ) ) {
344
+ $args['placement_type'] = $placement['type'];
345
  }
346
 
347
+ // options.
348
  $prefix = Advanced_Ads_Plugin::get_instance()->get_frontend_prefix();
349
 
350
+ // return either ad or group content.
351
  switch ( $_item[0] ) {
352
  case 'ad':
353
+ case Advanced_Ads_Select::AD:
354
+ // create class from placement id (not if header injection).
355
+ if ( ! isset( $placement['type'] ) || 'header' !== $placement['type'] ) {
356
  if ( ! isset( $args['output'] ) ) {
357
  $args['output'] = array();
358
  }
365
  }
366
  }
367
 
368
+ // fix method id.
369
  $_item[0] = Advanced_Ads_Select::AD;
370
  break;
371
 
372
+ // avoid loops (programmatical error).
373
+ case Advanced_Ads_Select::PLACEMENT:
374
  return;
375
 
376
+ case Advanced_Ads_Select::GROUP:
377
+ $class = $prefix . $id;
378
+ if ( ( isset( $placement['type'] ) && $placement['type'] !== 'header' )
379
+ && ( ! isset( $args['output']['class'] )
380
+ || ! is_array( $args['output']['class'] )
381
+ || ! in_array( $class, $args['output']['class'] ) ) ) {
382
+ $args['output']['class'][] = $class;
383
+ }
384
  default:
385
  }
386
 
387
+ // create placement id for various features.
388
  $args['output']['placement_id'] = $id;
389
 
390
+ // add the placement to the global output array.
391
  $advads = Advanced_Ads::get_instance();
392
+ $name = isset( $placement['name'] ) ? $placement['name'] : $id;
393
 
394
  if ( ! isset( $args['global_output'] ) || $args['global_output'] ) {
395
+ $advads->current_ads[] = array(
396
+ 'type' => 'placement',
397
+ 'id' => $id,
398
+ 'title' => $name,
399
+ );
400
  }
401
 
402
  $result = Advanced_Ads_Select::get_instance()->get_ad_by_method( (int) $_item[1], $_item[0], $args );
403
 
404
  return $result;
405
  }
406
+
407
+ return;
408
  }
409
 
410
  /**
411
+ * Inject ads directly into the content
412
  *
413
+ * @param string $placement_id Id of the placement.
414
+ * @param array $placement_opts Placement options.
415
+ * @param string $content Content to inject placement into.
416
+ *
417
+ * @return string $content Content with injected placement.
418
  * @since 1.2.1
 
 
 
 
 
419
  */
420
+ public static function &inject_in_content( $placement_id, $placement_opts, &$content ) {
421
  if ( ! extension_loaded( 'dom' ) ) {
422
  return $content;
423
  }
424
 
425
+ // get plugin options.
426
  $plugin_options = Advanced_Ads::get_instance()->options();
 
 
 
427
 
428
+ $wp_charset = get_bloginfo( 'charset' );
429
+ // parse document as DOM (fragment - having only a part of an actual post given).
430
+
431
+ $content_to_load = self::get_content_to_load( $content, $wp_charset );
432
  if ( ! $content_to_load ) {
433
  return $content;
434
  }
435
 
436
+ $dom = new DOMDocument( '1.0', $wp_charset );
437
+ // may loose some fragments or add autop-like code.
438
+ libxml_use_internal_errors( true ); // avoid notices and warnings - html is most likely malformed.
439
 
440
+ $success = $dom->loadHtml( '<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=' . $wp_charset . '" /><body>' . $content_to_load );
441
+ libxml_use_internal_errors( false );
442
+ if ( true !== $success ) {
443
  // -TODO handle cases were dom-parsing failed (at least inform user)
444
  return $content;
445
  }
446
 
447
+ // parse arguments.
448
+ $tag = isset( $placement_opts['tag'] ) ? $placement_opts['tag'] : 'p';
449
+ $tag = preg_replace( '/[^a-z0-9]/i', '', $tag ); // simplify tag.
450
 
451
+ // allow more complex xPath expression.
452
  $tag = apply_filters( 'advanced-ads-placement-content-injection-xpath', $tag, $placement_opts );
453
 
454
+ if ( 'pwithoutimg' === $tag ) {
455
  $tag = 'p[not(descendant::img)]';
456
  }
457
 
458
+ // select positions.
459
+ $xpath = new DOMXPath( $dom );
460
+ $items = $xpath->query( '/html/body/' . $tag );
461
 
462
  $options = array(
463
+ 'allowEmpty' => false, // whether the tag can be empty to be counted.
464
+ 'paragraph_select_from_bottom' => isset( $placement_opts['start_from_bottom'] ) && $placement_opts['start_from_bottom'],
465
+ // only has before and after.
466
+ 'before' => isset( $placement_opts['position'] ) && 'before' === $placement_opts['position'],
467
  );
468
 
469
+ $options['paragraph_id'] = isset( $placement_opts['index'] ) ? $placement_opts['index'] : 1;
470
  $options['paragraph_id'] = max( 1, (int) $options['paragraph_id'] );
471
 
472
+ // if there are too few items at this level test nesting.
473
+ $options['itemLimit'] = 'p' === $tag ? 2 : 1;
474
 
475
+ // trigger such a high item limit that all elements will be considered.
476
+ if ( ! empty( $plugin_options['content-injection-level-disabled'] ) ) {
477
  $options['itemLimit'] = 1000;
478
  }
479
+
480
+ // allow hooks to change some options.
481
  $options = apply_filters(
482
  'advanced-ads-placement-content-injection-options',
483
  $options,
484
+ $tag
485
+ );
486
 
487
+ if ( $items->length < $options['itemLimit'] ) {
488
+ $items = $xpath->query( '/html/body/*/' . $tag );
489
  }
490
+ // try third level.
491
  if ( $items->length < $options['itemLimit'] ) {
492
+ $items = $xpath->query( '/html/body/*/*/' . $tag );
493
  }
494
+ // try all levels as last resort.
495
  if ( $items->length < $options['itemLimit'] ) {
496
  $items = $xpath->query( '//' . $tag );
497
  }
498
+
499
+ // allow to select other elements.
500
  $items = apply_filters( 'advanced-ads-placement-content-injection-items', $items, $xpath, $tag );
501
 
502
+ // filter empty tags from items.
503
+ $whitespaces = json_decode( '"\t\n\r \u00A0"' );
504
+ $paragraphs = array();
505
+ foreach ( $items as $item ) {
506
+ if ( $options['allowEmpty'] || ( isset( $item->textContent ) && trim( $item->textContent, $whitespaces ) !== '' ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
507
  $paragraphs[] = $item;
508
  }
509
  }
510
 
511
+ $ancestors_to_limit = self::get_ancestors_to_limit( $xpath );
512
+ $paragraphs = self::filter_by_ancestors_to_limit( $paragraphs, $ancestors_to_limit );
513
+
514
+ $options['paragraph_count'] = count( $paragraphs );
515
 
516
+ if ( $options['paragraph_count'] >= $options['paragraph_id'] ) {
517
+ $offset = $options['paragraph_select_from_bottom'] ? $options['paragraph_count'] - $options['paragraph_id'] : $options['paragraph_id'] - 1;
518
+ $offsets = apply_filters( 'advanced-ads-placement-content-offsets', array( $offset ), $options, $placement_opts );
519
  $did_inject = false;
520
 
521
  foreach ( $offsets as $offset ) {
522
+ // inject.
523
+ $node = apply_filters( 'advanced-ads-placement-content-injection-node', $paragraphs[ $offset ], $tag, $options['before'] );
524
 
525
+ $ad_content = Advanced_Ads_Select::get_instance()->get_ad_by_method( $placement_id, 'placement', $placement_opts );
526
 
527
+ if ( trim( $ad_content, $whitespaces ) === '' ) {
528
  continue;
529
  }
530
 
531
+ // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
532
+ $ad_content = self::filter_ad_content( $ad_content, $node->tagName, $options );
533
 
534
  // convert HTML to XML!
535
+ $ad_dom = new DOMDocument( '1.0', $wp_charset );
536
+ libxml_use_internal_errors( true );
537
+ $ad_dom->loadHtml( '<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=' . $wp_charset . '" /><body>' . $ad_content );
538
+ // log errors.
539
+ if ( defined( 'WP_DEBUG' ) && WP_DEBUG && current_user_can( 'advanced_ads_manage_options' ) ) {
540
+ foreach ( libxml_get_errors() as $_error ) {
541
+ // continue, if there is '&' symbol, but not HTML entity.
542
  if ( false === stripos( $_error->message, 'htmlParseEntityRef:' ) ) {
543
  Advanced_Ads::log( 'possible content injection error for placement "' . $placement_id . '": ' . print_r( $_error, true ) );
544
  }
545
  }
546
  }
547
 
 
548
  if ( $options['before'] ) {
549
+ $ref_node = $node;
550
 
551
+ foreach ( $ad_dom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
552
  $importedNode = $dom->importNode( $importedNode, true );
553
+ $ref_node->parentNode->insertBefore( $importedNode, $ref_node );
554
  }
555
  } else {
556
+ // append before next node or as last child to body.
557
+ $ref_node = $node->nextSibling;
558
+ if ( isset( $ref_node ) ) {
559
 
560
+ foreach ( $ad_dom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
561
  $importedNode = $dom->importNode( $importedNode, true );
562
+ $ref_node->parentNode->insertBefore( $importedNode, $ref_node );
563
  }
 
564
  } else {
565
+ // append to body; -TODO using here that we only select direct children of the body tag.
566
+ foreach ( $ad_dom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
567
  $importedNode = $dom->importNode( $importedNode, true );
568
  $node->parentNode->appendChild( $importedNode );
569
  }
570
  }
571
  }
572
 
573
+ libxml_use_internal_errors( false );
574
  $did_inject = true;
575
  }
576
 
579
  }
580
 
581
  $content_orig = $content;
582
+ // convert to text-representation.
583
  $content = $dom->saveHTML();
584
  $content = self::prepare_output( $content, $content_orig );
585
 
586
+ /**
587
+ * Show a warning to ad admins in the Ad Health bar in the frontend, when
588
+ *
589
+ * * the level limitation was not disabled
590
+ * * could not inject one ad (as by use of `elseif` here)
591
+ * * but there are enough elements on the site, but just in sub-containers
592
+ */
593
+ } elseif ( current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options' ) )
594
+ && empty( $plugin_options['content-injection-level-disabled'] ) ) {
595
+
596
+ // Check if there are more elements without limitation.
 
597
  $all_items = $xpath->query( '//' . $tag );
598
+
599
+ $paragraphs = array();
600
+ foreach ( $all_items as $item ) {
601
+ if ( $options['allowEmpty'] || ( isset( $item->textContent ) && trim( $item->textContent, $whitespaces ) !== '' ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
602
+ $paragraphs[] = $item;
603
+ }
604
+ }
605
+
606
+ $paragraphs = self::filter_by_ancestors_to_limit( $paragraphs, $ancestors_to_limit );
607
+ if ( $options['paragraph_id'] <= count( $paragraphs ) ) {
608
+ // Add a warning to ad health.
609
  add_filter( 'advanced-ads-ad-health-nodes', array( 'Advanced_Ads_Placements', 'add_ad_health_node' ) );
610
  }
611
  }
617
  * Get content to load.
618
  *
619
  * @param string $content Original content.
620
+ * @param string $wp_charset blog charset.
621
+ *
622
  * @return string $content Content to load.
623
  */
624
+ private static function get_content_to_load( $content, $wp_charset ) {
625
  $plugin_options = Advanced_Ads::get_instance()->options();
626
 
627
  // Prevent removing closing tags in scripts.
628
+ $content_to_load = preg_replace( '/<script.*?<\/script>/', '<!--\0-->', $content );
629
 
630
+ // check which priority the wpautop filter has; might have been disabled on purpose.
631
+ $wpautop_priority = has_filter( 'the_content', 'wpautop' );
632
  if ( $wpautop_priority && Advanced_Ads_Plugin::get_instance()->get_content_injection_priority() < $wpautop_priority ) {
633
  $content_to_load = wpautop( $content_to_load );
634
  }
639
  /**
640
  * Filter ad content.
641
  *
642
+ * @param string $ad_content Ad content.
643
  * @param string $tag_name tar before/after the content.
644
+ * @param array $options Injection options.
645
+ *
646
+ * @return string ad content.
647
  */
648
  private static function filter_ad_content( $ad_content, $tag_name, $options ) {
649
  $plugin_options = Advanced_Ads::get_instance()->options();
650
 
651
+ // Inject placeholder.
652
+ $id = count( self::$ads_for_placeholders );
653
  self::$ads_for_placeholders[] = array(
654
+ 'id' => $id,
655
+ 'tag' => $tag_name,
656
  'type' => $options['before'] ? 'before' : 'after',
657
+ 'ad' => $ad_content,
658
  );
659
+ $ad_content = '%advads_placeholder_' . $id . '%';
660
 
661
  return $ad_content;
662
  }
666
  *
667
  * @param string $content Modified content.
668
  * @param string $content_orig Original content.
669
+ *
670
  * @return string $content Content to output.
671
  */
672
  private static function prepare_output( $content, $content_orig ) {
673
  $plugin_options = Advanced_Ads::get_instance()->options();
674
 
675
+ $content = self::inject_ads( $content, $content_orig, self::$ads_for_placeholders );
676
  self::$ads_for_placeholders = array();
677
 
678
  return $content;
682
  * Search for ad placeholders in the `$content` to determine positions at which to inject ads.
683
  * Given the positions, inject ads into `$content_orig.
684
  *
685
+ * @param string $content Post content with injected ad placeholders.
686
  * @param string $content_orig Unmodified post content.
687
+ * @param array $options Injection options.
688
+ * @param array $ads_for_placeholders Array of ads.
689
+ * Each ad contains placeholder id, before or after which tag to inject the ad, the ad content.
690
+ *
691
  * @return string $content
692
  */
693
  private static function inject_ads( $content, $content_orig, $ads_for_placeholders ) {
694
+ $self_closing_tags = array(
695
+ 'area',
696
+ 'base',
697
+ 'basefont',
698
+ 'bgsound',
699
+ 'br',
700
+ 'col',
701
+ 'embed',
702
+ 'frame',
703
+ 'hr',
704
+ 'img',
705
+ 'input',
706
+ 'keygen',
707
+ 'link',
708
+ 'meta',
709
+ 'param',
710
+ 'source',
711
+ 'track',
712
+ 'wbr',
713
+ );
714
 
715
  // It is not possible to append/prepend in self closing tags.
716
  foreach ( $ads_for_placeholders as &$ad_content ) {
717
  if ( ( 'prepend' === $ad_content['type'] || 'append' === $ad_content['type'] )
718
+ && in_array( $ad_content['tag'], $self_closing_tags, true ) ) {
719
  $ad_content['type'] = 'after';
720
  }
721
  }
722
  unset( $ad_content );
723
  usort( $ads_for_placeholders, array( 'Advanced_Ads_Placements', 'sort_ads_for_placehoders' ) );
724
 
 
725
  // Add tags before/after which ad placehoders were injected.
726
  foreach ( $ads_for_placeholders as $ad_content ) {
727
  $tag = $ad_content['tag'];
728
 
729
+ switch ( $ad_content['type'] ) {
730
+ case 'before':
731
+ case 'prepend':
 
 
 
 
732
  $alts[] = "<${tag}[^>]*>";
733
+ break;
734
+ case 'after':
735
+ if ( in_array( $tag, $self_closing_tags, true ) ) {
736
+ $alts[] = "<${tag}[^>]*>";
737
+ } else {
738
+ $alts[] = "</${tag}>";
739
+ }
740
+ break;
741
+ case 'append':
742
  $alts[] = "</${tag}>";
743
+ break;
 
 
 
 
744
  }
 
745
  }
746
+ $alts = array_unique( $alts );
747
  $tag_regexp = implode( '|', $alts );
748
  // Add ad placeholder.
749
+ $alts[] = '%advads_placeholder_(?:\d+)%';
750
  $tag_and_placeholder_regexp = implode( '|', $alts );
751
 
752
  preg_match_all( "#{$tag_and_placeholder_regexp}#i", $content, $tag_matches );
755
  // For each tag located before/after an ad placeholder, find its offset among the same tags.
756
  foreach ( $tag_matches[0] as $r ) {
757
  if ( preg_match( '/%advads_placeholder_(\d+)%/', $r, $result ) ) {
758
+ $id = $result[1];
759
  $found_ad = false;
760
  foreach ( $ads_for_placeholders as $n => $ad ) {
761
  if ( (int) $ad['id'] === (int) $id ) {
767
  continue;
768
  }
769
 
770
+ switch ( $found_ad['type'] ) {
771
  case 'before':
772
  case 'append':
773
  $ads_for_placeholders[ $n ]['offset'] = $count;
774
  break;
775
  case 'after':
776
  case 'prepend':
777
+ $ads_for_placeholders[ $n ]['offset'] = $count - 1;
778
  break;
779
  }
 
780
  } else {
781
+ $count ++;
782
  }
783
  }
784
 
785
  // Find tags before/after which we need to inject ads.
786
  preg_match_all( "#{$tag_regexp}#i", $content_orig, $orig_tag_matches, PREG_OFFSET_CAPTURE );
787
  $new_content = '';
788
+ $pos = 0;
789
 
790
  foreach ( $orig_tag_matches[0] as $n => $r ) {
791
  $to_inject = array();
797
  }
798
 
799
  foreach ( $to_inject as $item ) {
800
+ switch ( $item['type'] ) {
801
+ case 'before':
802
+ case 'append':
803
+ $found_pos = $r[1];
804
+ break;
805
+ case 'after':
806
+ case 'prepend':
807
+ $found_pos = $r[1] + strlen( $r[0] );
808
+ break;
809
  }
810
 
811
  $new_content .= substr( $content_orig, $pos, $found_pos - $pos );
812
+ $pos = $found_pos;
813
  $new_content .= $item['ad'];
814
  }
815
  }
816
  $new_content .= substr( $content_orig, $pos );
817
+
818
  return $new_content;
819
  }
820
 
822
  /**
823
  * Callback function for usort() to sort ads for placeholders.
824
  *
825
+ * @param array $first The first array to compare.
826
  * @param array $second The second array to compare.
827
+ *
828
  * @return int 0 if both objects equal. -1 if second array should come first, 1 otherwise.
829
  */
830
  public static function sort_ads_for_placehoders( $first, $second ) {
832
  return 0;
833
  }
834
 
835
+ $num = array(
836
+ 'before' => 1,
837
+ 'prepend' => 2,
838
+ 'append' => 3,
839
+ 'after' => 4,
840
+ );
841
 
842
+ return $num[ $first['type'] ] > $num[ $second['type'] ] ? 1 : - 1;
843
  }
844
 
845
  /**
846
  * Add a warning to 'Ad health'.
847
  *
848
+ * @param array $nodes .
849
+ *
850
  * @return array $nodes.
851
  */
852
  public static function add_ad_health_node( $nodes ) {
853
+ $nodes[] = array(
854
+ 'type' => 1,
855
+ 'data' => array(
856
+ 'parent' => 'advanced_ads_ad_health',
857
+ 'id' => 'advanced_ads_ad_health_the_content_not_enough_elements',
858
+ 'title' => sprintf(
859
  /* translators: %s stands for the name of the "Disable level limitation" option and automatically translated as well */
860
+ __( 'Set <em>%s</em> to show more ads', 'advanced-ads' ),
861
+ __( 'Disable level limitation', 'advanced-ads' )
862
+ ),
863
+ 'href' => admin_url( '/admin.php?page=advanced-ads-settings#top#general' ),
864
+ 'meta' => array(
865
+ 'class' => 'advanced_ads_ad_health_warning',
866
+ 'target' => '_blank',
867
+ ),
868
+ ),
869
+ );
870
+
871
  return $nodes;
872
  }
873
 
874
  /**
875
+ * Check if the placement can be displayed
876
+ *
877
+ * @param int $id placement id.
878
  *
 
 
879
  * @return bool true if placement can be displayed
880
+ * @since 1.6.9
881
  */
882
+ public static function can_display( $id = 0 ) {
883
+ if ( ! isset( $id ) || 0 === $id ) {
884
  return true;
885
  }
886
 
891
  * Get the placements that includes the ad or group.
892
  *
893
  * @param string $type 'ad' or 'group'.
894
+ * @param int $id Id.
895
+ *
896
  * @return array
897
  */
898
  public static function get_placements_by( $type, $id ) {
908
  return $result;
909
  }
910
 
911
+ /**
912
+ * Get paths of ancestors that should not contain ads.
913
+ *
914
+ * @param object $xpath DOMXPath object.
915
+ *
916
+ * @return array Paths of ancestors.
917
+ */
918
+ private static function get_ancestors_to_limit( $xpath ) {
919
+ $query = self::get_ancestors_to_limit_query();
920
+ if ( ! $query ) {
921
+ return array();
922
+ }
923
+
924
+ $node_list = $xpath->query( $query );
925
+ $ancestors_to_limit = array();
926
+
927
+ foreach ( $node_list as $a ) {
928
+ $ancestors_to_limit[] = $a->getNodePath();
929
+ }
930
+
931
+ return $ancestors_to_limit;
932
+ }
933
+
934
+
935
+ /**
936
+ * Remove paragraphs that has ancestors that should not contain ads.
937
+ *
938
+ * @param array $paragraphs An array of `DOMNode` objects to insert ads before or after.
939
+ * @param array $ancestors_to_limit Paths of ancestor that should not contain ads.
940
+ *
941
+ * @return array $new_paragraphs An array of `DOMNode` objects to insert ads before or after.
942
+ */
943
+ private static function filter_by_ancestors_to_limit( $paragraphs, $ancestors_to_limit ) {
944
+ $new_paragraphs = array();
945
+
946
+ foreach ( $paragraphs as $k => $paragraph ) {
947
+ foreach ( $ancestors_to_limit as $a ) {
948
+ if ( 0 === stripos( $paragraph->getNodePath(), $a ) ) {
949
+ continue 2;
950
+ }
951
+ }
952
+
953
+ $new_paragraphs[] = $paragraph;
954
+ }
955
+
956
+ return $new_paragraphs;
957
+ }
958
+
959
+ /**
960
+ * Get query to select ancestors that should not contain ads.
961
+ *
962
+ * @return string/false DOMXPath query or false.
963
+ */
964
+ private static function get_ancestors_to_limit_query() {
965
+ /**
966
+ * TODO:
967
+ * - support `%` (rand) at the start
968
+ * - support plain text that node should contain instead of CSS selectors
969
+ * - support `prev` and `next` as `type`
970
+ */
971
+
972
+ /**
973
+ * Filter the nodes that limit injection.
974
+ *
975
+ * @param array An array of arrays, each of which contains:
976
+ *
977
+ * @type string $type Accept: `ancestor` - limit injection inside the ancestor.
978
+ * @type string $node A "class selector" which targets one class (.) or "id selector" which targets one id (#),
979
+ * optionally with `%` at the end.
980
+ */
981
+ $items = apply_filters(
982
+ 'advanced-ads-content-injection-nodes-without-ads',
983
+ array(
984
+ array(
985
+ // a class anyone can use to prevent automatic ad injection into a specific element.
986
+ 'node' => '.advads-stop-injection',
987
+ 'type' => 'ancestor',
988
+ ),
989
+ array(
990
+ // Product Slider for Beaver Builder by WooPack.
991
+ 'node' => '.woopack-product-carousel',
992
+ 'type' => 'ancestor',
993
+ ),
994
+ array(
995
+ // WP Author Box Lite.
996
+ 'node' => '#wpautbox-%',
997
+ 'type' => 'ancestor',
998
+ ),
999
+ array(
1000
+ // GeoDirectory Post Slider.
1001
+ 'node' => '.geodir-post-slider',
1002
+ 'type' => 'ancestor',
1003
+ ),
1004
+ )
1005
+ );
1006
+
1007
+ $query = array();
1008
+ foreach ( $items as $p ) {
1009
+ $sel = $p['node'];
1010
+
1011
+ $sel_type = substr( $sel, 0, 1 );
1012
+ $sel = substr( $sel, 1 );
1013
+
1014
+ $rand_pos = strpos( $sel, '%' );
1015
+ $sel = str_replace( '%', '', $sel );
1016
+ $sel = sanitize_html_class( $sel );
1017
+
1018
+ if ( '.' === $sel_type ) {
1019
+ if ( false !== $rand_pos ) {
1020
+ $query[] = "@class and contains(concat(' ', normalize-space(@class), ' '), ' $sel')";
1021
+ } else {
1022
+ $query[] = "@class and contains(concat(' ', normalize-space(@class), ' '), ' $sel ')";
1023
+ }
1024
+ }
1025
+ if ( '#' === $sel_type ) {
1026
+ if ( false !== $rand_pos ) {
1027
+ $query[] = "@id and starts-with(@id, '$sel')";
1028
+ } else {
1029
+ $query[] = "@id and @id = '$sel'";
1030
+ }
1031
+ }
1032
+ }
1033
+
1034
+ if ( ! $query ) {
1035
+ return false;
1036
+ }
1037
+
1038
+ return '//*[' . implode( ' or ', $query ) . ']';
1039
+ }
1040
+
1041
+
1042
  }
1043
 
modules/ads-txt/includes/class-advanced-ads-ads-txt-utils.php CHANGED
@@ -8,13 +8,26 @@ class Advanced_Ads_Ads_Txt_Utils {
8
  /**
9
  * Get file info.
10
  *
 
11
  * @return array/WP_Error An array containing 'exists', 'is_third_party'.
12
  * A WP_Error upon error.
13
  */
14
  public static function get_file_info( $url = null ) {
15
  $url = $url ? $url : home_url( '/' );
16
 
17
- $response = wp_remote_get( trailingslashit( $url ) . 'ads.txt', array( 'timeout' => 3 ) );
 
 
 
 
 
 
 
 
 
 
 
 
18
  $code = wp_remote_retrieve_response_code( $response );
19
  $content = wp_remote_retrieve_body( $response );
20
  $content_type = wp_remote_retrieve_header( $response, 'content-type' );
8
  /**
9
  * Get file info.
10
  *
11
+ * @param string $url Url to retrieve the file.
12
  * @return array/WP_Error An array containing 'exists', 'is_third_party'.
13
  * A WP_Error upon error.
14
  */
15
  public static function get_file_info( $url = null ) {
16
  $url = $url ? $url : home_url( '/' );
17
 
18
+ // Disable ssl verification to prevent errors on servers that are not properly configured with its https certificates.
19
+ /** This filter is documented in wp-includes/class-wp-http-streams.php */
20
+ $sslverify = apply_filters( 'https_local_ssl_verify', false );
21
+ $response = wp_remote_get(
22
+ trailingslashit( $url ) . 'ads.txt',
23
+ array(
24
+ 'timeout' => 3,
25
+ 'sslverify' => $sslverify,
26
+ 'headers' => array(
27
+ 'Cache-Control' => 'no-cache',
28
+ ),
29
+ )
30
+ );
31
  $code = wp_remote_retrieve_response_code( $response );
32
  $content = wp_remote_retrieve_body( $response );
33
  $content_type = wp_remote_retrieve_header( $response, 'content-type' );
readme.txt CHANGED
@@ -4,7 +4,7 @@ Tags: ads, ad manager, ad rotation, adsense, banner
4
  Requires at least: 4.6
5
  Tested up to: 5.3
6
  Requires PHP: 5.6
7
- Stable tag: 1.17.2
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -304,6 +304,12 @@ Yes. Advanced Ads is based on WordPress standards and therefore easily customiza
304
 
305
  == Changelog ==
306
 
 
 
 
 
 
 
307
  = 1.17.2 =
308
 
309
  * replaced autoloader
4
  Requires at least: 4.6
5
  Tested up to: 5.3
6
  Requires PHP: 5.6
7
+ Stable tag: 1.17.3
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
304
 
305
  == Changelog ==
306
 
307
+ = 1.17.3 =
308
+
309
+ * prevented content injection into specific elements where ads cause issues
310
+ * assign `advads-stop-injection` class to any element into which you don’t want to automatically inject ads
311
+ * fixed possible cURL error when checking existing ads.txt file locally
312
+
313
  = 1.17.2 =
314
 
315
  * replaced autoloader