PluginProbe ʕ •ᴥ•ʔ
Advanced Ads – Ad Manager & AdSense / 1.14.10
Advanced Ads – Ad Manager & AdSense v1.14.10
2.0.23 2.0.22 2.0.21 1.38.0 1.39.0 1.39.1 1.39.2 1.39.3 1.39.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.40.0 1.40.1 1.40.2 1.41.0 1.42.0 1.42.1 1.42.2 1.43.0 1.44.0 1.44.1 1.45.0 1.45.1 1.46.0 1.47.0 1.47.1 1.47.2 1.47.3 1.47.4 1.47.5 1.48.0 1.48.1 1.49.0 1.5.0 1.5.0.1 1.5.1 1.5.2 1.5.2.1 1.5.4 1.5.4.1 1.5.5 1.50.0 1.51.0 1.51.1 1.51.2 1.51.3 1.52.0 1.52.1 1.52.2 1.52.3 1.52.4 1.53.0 1.53.1 1.53.2 1.54.0 1.54.1 1.55.0 1.56.0 1.56.1 1.56.2 1.56.3 1.56.4 1.6 1.6.1 1.6.10 1.6.10.1 1.6.10.2 1.6.11 1.6.11.1 1.6.12 1.6.13 1.6.14 1.6.15 1.6.16 1.6.17 1.6.17.1 1.6.17.2 1.6.2 1.6.2.1 1.6.3 1.6.4 1.6.4.1 1.6.5 1.6.6 1.6.6.1 1.6.7 1.6.7.1 1.6.8 1.6.8.1 1.6.8.2 1.6.8.3 1.6.9 1.6.9.1 1.6.9.2 1.6.9.3 1.6.9.4 1.7 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.1.1 1.7.1.2 1.7.1.3 1.7.1.4 1.7.1.5 1.7.10 trunk 1.7.11 1.0.1 1.7.12 1.0.2 1.7.13 1.0.3 1.7.14 1.1.0 1.7.15 1.1.1 1.7.16 1.1.2 1.7.17 1.1.3 1.7.18 1.10 1.7.19 1.10.1 1.7.2 1.10.10 1.7.2.1 1.10.11 1.7.20 1.10.12 1.7.21 1.10.2 1.7.22 1.10.3 1.7.23 1.10.4 1.7.24 1.10.5 1.7.25 1.10.6 1.7.3 1.10.7 1.7.4 1.10.8 1.7.4.1 1.10.9 1.7.4.2 1.11 1.7.4.3 1.11.1 1.7.4.4 1.11.2 1.7.4.5 1.12 1.7.5 1.13 1.7.5.1 1.13.1 1.7.6 1.13.2 1.7.7 1.13.3 1.7.8 1.13.4 1.7.9 1.13.5 1.7.9.1 1.13.6 1.7.9.2 1.13.7 1.7.9.3 1.13.8 1.8 1.14 1.8.1 1.14.1 1.8.10 1.14.10 1.8.11 1.14.11 1.8.12 1.14.2 1.8.13 1.14.3 1.8.14 1.14.4 1.8.15 1.14.5 1.8.16 1.14.6 1.8.17 1.14.7 1.8.18 1.14.8 1.8.19 1.14.9 1.8.2 1.15 1.8.20 1.16 1.8.21 1.16.1 1.8.22 1.17 1.8.23 1.17.1 1.8.24 1.17.10 1.8.25 1.17.10-rc.1 1.8.26 1.17.11 1.8.27 1.17.12 1.8.28 1.17.12-rc.1 1.8.29 1.17.2 1.8.3 1.17.3 1.8.30 1.17.4 1.8.4 1.17.5 1.8.5 1.17.6 1.8.6 1.17.7 1.8.7 1.17.8 1.8.8 1.17.9 1.8.9 1.17.9-beta.1 1.9 1.18.0 2.0.0 1.19.0 2.0.1 1.19.1 2.0.10 1.2 2.0.11 1.2.1 2.0.12 1.2.2 2.0.13 1.2.3 2.0.14 1.2.4 2.0.15 1.2.5 2.0.16 1.2.6 2.0.17 1.2.7 2.0.18 1.20.0 2.0.19 1.20.0-rc.1 2.0.2 1.20.0-rc.2 2.0.20 1.20.1 2.0.3 1.20.2 2.0.4 1.20.3 2.0.5 1.21.0 2.0.6 1.21.1 2.0.7 1.22.0 2.0.8 1.22.1 2.0.9 1.22.2 1.23.0 1.23.1 1.23.2 1.24.0 1.24.1 1.24.2 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.29.0 1.29.1 1.3 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.14 1.3.15 1.3.16 1.3.17 1.3.18 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.30.0 1.30.1 1.30.2 1.30.2-rc.1 1.30.3 1.30.4 1.30.4-rc.1 1.30.5 1.31.0 1.31.1 1.32.0 1.32.0-rc.1 1.33.0 1.33.1 1.33.2 1.34.0 1.35.0 1.35.1 1.36.0 1.36.1 1.36.2 1.36.3 1.37.0 1.37.1 1.37.2
advanced-ads / classes / ad_placements.php
advanced-ads / classes Last commit date
EDD_SL_Plugin_Updater.php 6 years ago ad-ajax.php 6 years ago ad-debug.php 8 years ago ad-health-notices.php 6 years ago ad-model.php 8 years ago ad-select.php 9 years ago ad.php 6 years ago ad_ajax_callbacks.php 6 years ago ad_group.php 6 years ago ad_placements.php 6 years ago ad_type_abstract.php 8 years ago ad_type_content.php 6 years ago ad_type_dummy.php 6 years ago ad_type_group.php 8 years ago ad_type_image.php 6 years ago ad_type_plain.php 6 years ago checks.php 6 years ago compatibility.php 6 years ago display-conditions.php 6 years ago filesystem.php 8 years ago frontend_checks.php 6 years ago plugin.php 6 years ago upgrades.php 6 years ago utils.php 6 years ago visitor-conditions.php 6 years ago widget.php 6 years ago
ad_placements.php
636 lines
1 <?php
2
3 /**
4 * Advanced Ads
5 *
6 * @package Advanced_Ads_Placements
7 * @author Thomas Maier <thomas.maier@webgilde.com>
8 * @license GPL-2.0+
9 * @link http://webgilde.com
10 * @copyright 2014 Thomas Maier, webgilde GmbH
11 */
12
13 /**
14 * grouping placements functions
15 *
16 * @since 1.1.0
17 * @package Advanced_Ads_Placements
18 * @author Thomas Maier <thomas.maier@webgilde.com>
19 */
20 class Advanced_Ads_Placements {
21
22 /**
23 * get placement types
24 *
25 * @since 1.2.1
26 * @return arr $types array with placement types
27 */
28 public static function get_placement_types() {
29 $types = array(
30 'default' => array(
31 'title' => __( 'Manual Placement', 'advanced-ads' ),
32 'description' => __( 'Manual placement to use as function or shortcode.', 'advanced-ads' ),
33 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/manual.png',
34 'options' => array( 'show_position' => true, 'show_lazy_load' => true, 'amp' => true )
35 ),
36 'header' => array(
37 'title' => __( 'Header Code', 'advanced-ads' ),
38 'description' => __( 'Injected in Header (before closing &lt;/head&gt; Tag, often not visible).', 'advanced-ads' ),
39 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/header.png'
40 ),
41 'footer' => array(
42 'title' => __( 'Footer Code', 'advanced-ads' ),
43 'description' => __( 'Injected in Footer (before closing &lt;/body&gt; Tag).', 'advanced-ads' ),
44 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/footer.png',
45 'options' => array( 'amp' => true )
46 ),
47 'post_top' => array(
48 'title' => __( 'Before Content', 'advanced-ads' ),
49 'description' => __( 'Injected before the post content.', 'advanced-ads' ),
50 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/content-before.png',
51 'options' => array( 'show_position' => true, 'show_lazy_load' => true, 'uses_the_content' => true, 'amp' => true )
52 ),
53 'post_bottom' => array(
54 'title' => __( 'After Content', 'advanced-ads' ),
55 'description' => __( 'Injected after the post content.', 'advanced-ads' ),
56 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/content-after.png',
57 'options' => array( 'show_position' => true, 'show_lazy_load' => true, 'uses_the_content' => true, 'amp' => true )
58 ),
59 'post_content' => array(
60 'title' => __( 'Content', 'advanced-ads' ),
61 'description' => __( 'Injected into the content. You can choose the paragraph after which the ad content is displayed.', 'advanced-ads' ),
62 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/content-within.png',
63 'options' => array( 'show_position' => true, 'show_lazy_load' => true, 'uses_the_content' => true, 'amp' => true )
64 ),
65 'sidebar_widget' => array(
66 'title' => __( 'Sidebar Widget', 'advanced-ads' ),
67 'description' => __( 'Create a sidebar widget with an ad. Can be placed and used like any other widget.', 'advanced-ads' ),
68 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/widget.png',
69 'options' => array( 'show_lazy_load' => true, 'amp' => true )
70 ),
71 );
72 return apply_filters( 'advanced-ads-placement-types', $types );
73 }
74
75 /**
76 * update placements if sent
77 *
78 * @since 1.5.2
79 */
80 static function update_placements(){
81
82 // check user permissions
83 if( ! current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_placements') ) ) {
84 return;
85 }
86
87 $success = null;
88
89 // add hook of last opened placement settings to URL
90 $hook = !empty( $_POST['advads-last-edited-placement'] ) ? '#single-placement-' . $_POST['advads-last-edited-placement'] : '';
91
92 if ( isset($_POST['advads']['placement']) && check_admin_referer( 'advads-placement', 'advads_placement' ) ){
93 $success = self::save_new_placement( $_POST['advads']['placement'] );
94 }
95 // save placement data
96 if ( isset($_POST['advads']['placements']) && check_admin_referer( 'advads-placement', 'advads_placement' )){
97 $success = self::save_placements( $_POST['advads']['placements'] );
98 }
99
100 $success = apply_filters( 'advanced-ads-update-placements', $success );
101
102 if(isset($success)){
103 $message = $success ? 'updated' : 'error';
104 wp_redirect( esc_url_raw( add_query_arg(array('message' => $message) ) ) . $hook );
105 }
106 }
107
108 /**
109 * save a new placement
110 *
111 * @since 1.1.0
112 * @param array $new_placement
113 * @return mixed slug if saved; false if not
114 */
115 public static function save_new_placement($new_placement) {
116 // load placements // -TODO use model
117 $placements = Advanced_Ads::get_ad_placements_array();
118
119 // create slug
120 $new_placement['slug'] = sanitize_title( $new_placement['name'] );
121
122 if ( isset( $placements[ $new_placement['slug'] ] ) ) {
123 $i = 1;
124 // try to save placement until we found an empty slug
125 do {
126 $i ++;
127 if ( $i === 100 ) { // prevent endless loop, just in case
128 Advanced_Ads::log( 'endless loop when injecting placement' );
129 break;
130 }
131 } while ( isset( $placements[ $new_placement['slug'] . '_' . $i ] ) );
132
133 $new_placement['slug'] .= '_' . $i;
134 $new_placement['name'] .= ' ' . $i;
135 }
136
137 // check if slug already exists or is empty
138 if ( $new_placement['slug'] === '' || isset( $placements[$new_placement['slug']]) || !isset( $new_placement['type'] ) ) {
139 return false;
140 }
141
142 // make sure only allowed types are being saved
143 $placement_types = Advanced_Ads_Placements::get_placement_types();
144 $new_placement['type'] = (isset($placement_types[$new_placement['type']])) ? $new_placement['type'] : 'default';
145 // escape name
146 $new_placement['name'] = esc_attr( $new_placement['name'] );
147
148 // add new place to all placements
149 $placements[$new_placement['slug']] = array(
150 'type' => $new_placement['type'],
151 'name' => $new_placement['name'],
152 'item' => $new_placement['item']
153 );
154
155 // add index options
156 if ( isset($new_placement['options']) ){
157 $placements[$new_placement['slug']]['options'] = $new_placement['options'];
158 if ( isset($placements[$new_placement['slug']]['options']['index']) ) {
159 $placements[$new_placement['slug']]['options']['index'] = absint( $placements[$new_placement['slug']]['options']['index'] ); }
160 }
161
162 // save array
163 Advanced_Ads::get_instance()->get_model()->update_ad_placements_array( $placements );
164
165 return $new_placement['slug'];
166 }
167
168 /**
169 * save placements
170 *
171 * @since 1.1.0
172 * @param array $placement_items
173 * @return mixed true if saved; error message if not
174 */
175 public static function save_placements($placement_items) {
176
177 // load placements // -TODO use model
178 $placements = Advanced_Ads::get_ad_placements_array();
179
180 foreach ( $placement_items as $_placement_slug => $_placement ) {
181 // remove the placement
182 if ( isset($_placement['delete']) ) {
183 unset($placements[$_placement_slug]);
184 continue;
185 }
186 // save item
187 if ( isset($_placement['item']) ) {
188 $placements[$_placement_slug]['item'] = $_placement['item']; }
189 // save item options
190 if ( isset($_placement['options']) ){
191 $placements[$_placement_slug]['options'] = $_placement['options'];
192 if ( isset($placements[$_placement_slug]['options']['index']) ) {
193 $placements[$_placement_slug]['options']['index'] = absint( $placements[$_placement_slug]['options']['index'] ); }
194 } else {
195 $placements[$_placement_slug]['options'] = array();
196 }
197 }
198
199 // save array
200 Advanced_Ads::get_instance()->get_model()->update_ad_placements_array( $placements );
201
202 return true;
203 }
204
205 /**
206 * get items for item select field
207 *
208 * @since 1.1
209 * @return arr $select items for select field
210 */
211 public static function items_for_select() {
212 $select = array();
213 $model = Advanced_Ads::get_instance()->get_model();
214
215 // load all ad groups
216 $groups = $model->get_ad_groups();
217 foreach ( $groups as $_group ) {
218 $select['groups']['group_' . $_group->term_id] = $_group->name;
219 }
220
221 // load all ads
222 $ads = $model->get_ads( array('orderby' => 'title', 'order' => 'ASC') );
223 foreach ( $ads as $_ad ) {
224 $select['ads']['ad_' . $_ad->ID] = $_ad->post_title;
225 }
226
227 return $select;
228 }
229
230 /**
231 * get html tags for content injection
232 *
233 * @since 1.3.5
234 * @return arr $tags array with tags that can be used for content injection
235 */
236 public static function tags_for_content_injection(){
237 $tags = apply_filters( 'advanced-ads-tags-for-injection', array(
238 'p' => sprintf( __( 'paragraph (%s)', 'advanced-ads' ), '&lt;p&gt;' ),
239 'pwithoutimg' => sprintf( __( 'paragraph without image (%s)', 'advanced-ads' ), '&lt;p&gt;' ),
240 'h2' => sprintf( __( 'headline 2 (%s)', 'advanced-ads' ), '&lt;h2&gt;' ),
241 'h3' => sprintf( __( 'headline 3 (%s)', 'advanced-ads' ), '&lt;h3&gt;' ),
242 'h4' => sprintf( __( 'headline 4 (%s)', 'advanced-ads' ), '&lt;h4&gt;' ),
243 ));
244
245 return $tags;
246 }
247
248 /**
249 * return content of a placement
250 *
251 * @since 1.1.0
252 * @param string $id slug of the display
253 * @param array $args optional arguments (passed to child)
254 */
255 public static function output( $id = '', $args = array() ) {
256 // get placement data for the slug
257 if ( $id == '' ) {
258 return;
259 }
260
261 $placements = Advanced_Ads::get_ad_placements_array();
262 $placement = ( isset( $placements[ $id ] ) && is_array( $placements[ $id ] ) ) ? $placements[ $id ] : array();
263
264 if ( isset( $args['change-placement'] ) ) {
265 // some options was provided by the user
266 $placement = Advanced_Ads_Utils::merge_deep_array( array( $placement, $args['change-placement'] ) ) ;
267 }
268
269 if ( isset( $placement['item'] ) && $placement['item'] !== '' ) {
270 $_item = explode( '_', $placement['item'] );
271
272 if ( ! isset( $_item[1] ) || empty( $_item[1] ) ) {
273 return ;
274 }
275
276 // inject options
277 if ( isset( $placement['options'] ) && is_array( $placement['options'] ) ) {
278 foreach ( $placement['options'] as $_k => $_v ) {
279 if ( ! isset( $args[ $_k ] ) ) {
280 $args[ $_k ] = $_v;
281 }
282 }
283 }
284
285 // inject placement type
286 if ( isset( $placement['type'] ) ) {
287 $args[ 'placement_type' ] = $placement['type'];
288 }
289
290 // options
291 $prefix = Advanced_Ads_Plugin::get_instance()->get_frontend_prefix();
292
293 // return either ad or group content
294 switch ( $_item[0] ) {
295 case 'ad':
296 case Advanced_Ads_Select::AD :
297 // create class from placement id (not if header injection)
298 if ( ! isset( $placement['type'] ) || $placement['type'] !== 'header' ) {
299 if ( ! isset( $args['output'] ) ) {
300 $args['output'] = array();
301 }
302 if ( ! isset( $args['output']['class'] ) ) {
303 $args['output']['class'] = array();
304 }
305 $class = $prefix . $id;
306 if ( ! in_array( $class, $args['output']['class'] ) ) {
307 $args['output']['class'][] = $class;
308 }
309 }
310
311 // fix method id
312 $_item[0] = Advanced_Ads_Select::AD;
313 break;
314
315 // avoid loops (programmatical error)
316 case Advanced_Ads_Select::PLACEMENT :
317 return;
318
319 case Advanced_Ads_Select::GROUP :
320 $class = $prefix . $id;
321 if ( ( isset( $placement['type'] ) && $placement['type'] !== 'header' )
322 && ( !isset( $args['output']['class'] )
323 || !is_array( $args['output']['class'] )
324 || !in_array( $class, $args['output']['class'] ) ) ) {
325 $args['output']['class'][] = $class;
326 }
327 default:
328 }
329
330 // create placement id for various features
331 $args['output']['placement_id'] = $id;
332
333 // add the placement to the global output array
334 $advads = Advanced_Ads::get_instance();
335 $name = isset( $placement['name'] ) ? $placement['name'] : $id;
336
337 if ( ! isset( $args['global_output'] ) || $args['global_output'] ) {
338 $advads->current_ads[] = array( 'type' => 'placement', 'id' => $id, 'title' => $name );
339 }
340
341 $result = Advanced_Ads_Select::get_instance()->get_ad_by_method( (int) $_item[1], $_item[0], $args );
342
343 if ( $result && ! empty( $args['placement_clearfix'] ) ) {
344 $result .= '<br style="clear: both; display: block; float: none; color:blue;"/>';
345 }
346
347 return $result;
348 }
349 }
350
351 /**
352 * inject ads directly into the content
353 *
354 * @since 1.2.1
355 * @param string $placement_id id of the placement
356 * @param arr $placement_opts placement options
357 * @param string $content
358 * @return type
359 * @link inspired by http://www.wpbeginner.com/wp-tutorials/how-to-insert-ads-within-your-post-content-in-wordpress/
360 */
361 public static function &inject_in_content($placement_id, $placement_opts, &$content) {
362 if ( ! extension_loaded( 'dom' ) ) {
363 return $content;
364 }
365
366 // get plugin options
367 $plugin_options = Advanced_Ads::get_instance()->options();
368
369 // parse document as DOM (fragment - having only a part of an actual post given)
370 // -TODO may want to verify the wpcharset is supported by server (mb_list_encodings)
371 // prevent messages from dom parser
372 $wpCharset = get_bloginfo('charset');
373 // check if mbstring exists
374 if ( ! function_exists( 'mb_convert_encoding' ) ) {
375 if ( $wpCharset === "UTF-8" ) {
376 $content_to_load = htmlspecialchars_decode( htmlentities( $content, ENT_COMPAT, $wpCharset, false ) );
377 } else {
378 return $content;
379 }
380 } else {
381 $content_to_load = mb_convert_encoding( $content, 'HTML-ENTITIES', $wpCharset );
382 }
383
384 // check which priority the wpautop filter has; might have been disabled on purpose
385 $wpautop_priority = has_filter( 'the_content', 'wpautop');
386 if ( $wpautop_priority && Advanced_Ads_Plugin::get_instance()->get_content_injection_priority() < $wpautop_priority ) {
387 $content_to_load = wpautop( $content_to_load );
388 }
389
390 // temporarily change content during processing
391 $replacements = array(
392 'gcse:search' => 'gcse__search', // Google custom search namespaced tags.
393 );
394 $content_to_load = str_replace( array_keys( $replacements ), array_values( $replacements ), $content_to_load );
395
396 $dom = new DOMDocument('1.0', $wpCharset);
397 // may loose some fragments or add autop-like code
398 libxml_use_internal_errors(true); // avoid notices and warnings - html is most likely malformed
399
400 $success = $dom->loadHtml('<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=' . $wpCharset . '" /><body>' . $content_to_load);
401 libxml_use_internal_errors(false);
402 if ($success !== true) {
403 // -TODO handle cases were dom-parsing failed (at least inform user)
404 return $content;
405 }
406
407 // parse arguments
408 $tag = isset($placement_opts['tag']) ? $placement_opts['tag'] : 'p';
409 $tag = preg_replace('/[^a-z0-9]/i', '', $tag); // simplify tag
410
411 // allow more complex xPath expression
412 $tag = apply_filters( 'advanced-ads-placement-content-injection-xpath', $tag, $placement_opts );
413
414 if ( $tag === 'pwithoutimg' ) {
415 $tag = 'p[not(descendant::img)]';
416 }
417
418 // select positions
419 $xpath = new DOMXPath($dom);
420 $items = $xpath->query('/html/body/' . $tag);
421
422 $options = array(
423 'allowEmpty' => false, // whether the tag can be empty to be counted
424 'paragraph_select_from_bottom' => isset($placement_opts['start_from_bottom']) && $placement_opts['start_from_bottom'],
425 // only has before and after
426 'before' => isset($placement_opts['position']) && $placement_opts['position'] === 'before'
427 );
428
429 $options['paragraph_id'] = isset($placement_opts['index']) ? $placement_opts['index'] : 1;
430 $options['paragraph_id'] = max( 1, (int) $options['paragraph_id'] );
431
432 // if there are too few items at this level test nesting
433 $options['itemLimit'] = $tag === 'p' ? 2 : 1;
434
435 // trigger such a high item limit that all elements will be considered
436 if( ! empty($plugin_options['content-injection-level-disabled'] ) ){
437 $options['itemLimit'] = 1000;
438 }
439
440 // allow hooks to change some options
441 $options = apply_filters(
442 'advanced-ads-placement-content-injection-options',
443 $options,
444 $tag );
445
446 if ($items->length < $options['itemLimit'] ) {
447 $items = $xpath->query('/html/body/*/' . $tag);
448 }
449 // try third level
450 if ( $items->length < $options['itemLimit'] ) {
451 $items = $xpath->query('/html/body/*/*/' . $tag);
452 }
453 // try all levels as last resort
454 if ( $items->length < $options['itemLimit'] ) {
455 $items = $xpath->query( '//' . $tag );
456 }
457
458 // allow to select other elements
459 $items = apply_filters( 'advanced-ads-placement-content-injection-items', $items, $xpath, $tag );
460
461 // filter empty tags from items
462 $whitespaces = json_decode('"\t\n\r \u00A0"');
463 $paragraphs = array();
464 foreach ($items as $item) {
465 if ( $options['allowEmpty'] || ( isset($item->textContent) && trim($item->textContent, $whitespaces) !== '' ) ) {
466 $paragraphs[] = $item;
467 }
468 }
469
470 $options['paragraph_count'] = count($paragraphs);
471
472 if ($options['paragraph_count'] >= $options['paragraph_id']) {
473 $offset = $options['paragraph_select_from_bottom'] ? $options['paragraph_count'] - $options['paragraph_id'] : $options['paragraph_id'] - 1;
474 $offsets = apply_filters( 'advanced-ads-placement-content-offsets', array( $offset ), $options, $placement_opts );
475 $did_inject = false;
476
477 foreach ( $offsets as $offset ) {
478 // test ad is emtpy
479 $adContent = Advanced_Ads_Select::get_instance()->get_ad_by_method( $placement_id, 'placement', $placement_opts );
480 if ( trim( $adContent, $whitespaces ) === '' ) {
481 continue;
482 }
483
484 // convert HTML to XML!
485 $adDom = new DOMDocument('1.0', $wpCharset);
486 libxml_use_internal_errors(true);
487 // replace `</` with `<\/` in ad content when placed within `document.write()` to prevent code from breaking
488 // source for this regex: http://stackoverflow.com/questions/17852537/preg-replace-only-specific-part-of-string
489 $adContent = preg_replace('#(document.write.+)</(.*)#', '$1<\/$2', $adContent); // escapes all closing html tags
490 // $adContent = preg_replace('#(document.write.+)</sc(.*)#', '$1<\/sc$2', $adContent); // only escapes closing </script> tags
491 // $adContent = preg_replace('#(document.write[^<^)]+)</sc(.*)#', '$1<\/sc$2', $adContent); // too restrict, doesn’t work when beginning <script> tag is in the same line
492 $adDom->loadHtml('<!DOCTYPE html><html><meta http-equiv="Content-Type" content="text/html; charset=' . $wpCharset . '" /><body>' . $adContent);
493 // log errors
494 if ( defined ( 'WP_DEBUG' ) && WP_DEBUG && current_user_can( 'advanced_ads_manage_options' ) ) {
495 foreach( libxml_get_errors() as $_error ) {
496 // continue, if there is '&' symbol, but not HTML entity
497 if ( false === stripos( $_error->message, 'htmlParseEntityRef:' ) ) {
498 Advanced_Ads::log( 'possible content injection error for placement "' . $placement_id . '": ' . print_r( $_error, true ) );
499 }
500 }
501 }
502
503 // inject
504 $node = apply_filters( 'advanced-ads-placement-content-injection-node', $paragraphs[$offset], $tag, $options['before'] );
505 if ( $options['before'] ) {
506 $refNode = $node;
507
508 foreach ( $adDom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
509 $importedNode = $dom->importNode( $importedNode, true );
510 $refNode->parentNode->insertBefore( $importedNode, $refNode );
511 }
512 } else {
513 // append before next node or as last child to body
514 $refNode = $node->nextSibling;
515 if (isset($refNode)) {
516
517 foreach ( $adDom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
518 $importedNode = $dom->importNode( $importedNode, true );
519 $refNode->parentNode->insertBefore( $importedNode, $refNode );
520 }
521
522 } else {
523 // append to body; -TODO using here that we only select direct children of the body tag
524 foreach ( $adDom->getElementsByTagName( 'body' )->item( 0 )->childNodes as $importedNode ) {
525 $importedNode = $dom->importNode( $importedNode, true );
526 $node->parentNode->appendChild( $importedNode );
527 }
528 }
529 }
530
531 libxml_use_internal_errors(false);
532 $did_inject = true;
533 }
534
535 if ( ! $did_inject ) {
536 return $content;
537 }
538
539 // convert to text-representation
540 $content = $dom->saveHTML();
541 // remove head and tail (required for dom parser but unwanted for content)
542 $content = substr($content, stripos($content, '<body>') + 6);
543 $content = str_replace(array('</body>', '</html>'), '', $content);
544 $content = str_replace( array_values( $replacements ), array_keys( $replacements ), $content );
545
546 // no fall-back desired: if there are too few paragraphs do nothing
547
548 // fix shortcode quotes (malformed by backend editor)
549 $matches = array();
550 if (0 < preg_match_all('/\[[^]]+\]/Siu', $content, $matches, PREG_OFFSET_CAPTURE) && isset($matches[0])) {
551 foreach ($matches[0] as $match) {
552 $offset = $match[1];
553 $content = substr($content, 0, $offset) . str_replace(array('', '', '&#8220;', '&quote;', '&#8243;'), '"', $match[0]) . substr($content, $offset + strlen($match[0]));
554 }
555 }
556 /**
557 * show a warning to ad admins in the Ad Health bar in the frontend, when
558 *
559 * * the level limitation was not disabled
560 * * could not inject one ad (as by use of `elseif` here)
561 * * but there are enough elements on the site, but just in sub-containers
562 *
563 */
564 } elseif( current_user_can( Advanced_Ads_Plugin::user_cap( 'advanced_ads_manage_options') )
565 && empty($plugin_options['content-injection-level-disabled'] ) ) {
566
567 // check if there are more elements without limitation
568 $all_items = $xpath->query( '//' . $tag );
569 if( $options['paragraph_id'] <= $all_items->length ){
570 // add a warning to ad health
571 add_filter( 'advanced-ads-ad-health-nodes', array( 'Advanced_Ads_Placements', 'add_ad_health_node' ) );
572 }
573 }
574
575 return $content;
576 }
577
578 /**
579 * Add a warning to 'Ad health'.
580 *
581 * @param array $nodes.
582 * @return array $nodes.
583 */
584 public static function add_ad_health_node( $nodes ) {
585 $nodes[] = array( 'type' => 1, 'data' => array(
586 'parent' => 'advanced_ads_ad_health',
587 'id' => 'advanced_ads_ad_health_the_content_not_enough_elements',
588 'title' => sprintf(
589 /* translators: %s stands for the name of the "Disable level limitation" option and automatically translated as well */
590 __( 'Set <em>%s</em> to show more ads', 'advanced-ads' ),
591 __('Disable level limitation', 'advanced-ads' ) ),
592 'href' => admin_url( '/admin.php?page=advanced-ads-settings#top#general' ),
593 'meta' => array(
594 'class' => 'advanced_ads_ad_health_warning',
595 'target' => '_blank'
596 )
597 ) );
598 return $nodes;
599 }
600
601 /**
602 * check if the placement can be displayed
603 *
604 * @since 1.6.9
605 * @param int $id placement id
606 * @return bool true if placement can be displayed
607 */
608 static function can_display( $id = 0 ){
609 if ( ! isset($id) || $id === 0 ) {
610 return true;
611 }
612
613 return apply_filters( 'advanced-ads-can-display-placement', true, $id );
614 }
615
616 /**
617 * Get the placements that includes the ad or group.
618 *
619 * @param string $type 'ad' or 'group'.
620 * @param int $id Id.
621 * @return array
622 */
623 public static function get_placements_by( $type, $id ) {
624 $result = array();
625
626 $placements = Advanced_Ads::get_ad_placements_array();
627 foreach ( $placements as $_id => $_placement ) {
628 if ( isset( $_placement['item'] ) && $_placement['item'] === $type . '_' . $id ) {
629 $result[ $_id ] = $_placement;
630 }
631 }
632
633 return $result;
634 }
635 }
636