PluginProbe ʕ •ᴥ•ʔ
Advanced Ads – Ad Manager & AdSense / 1.51.3
Advanced Ads – Ad Manager & AdSense v1.51.3
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
Advanced_Ads_Modal.php 2 years ago EDD_SL_Plugin_Updater.php 2 years ago ad-ajax.php 2 years ago ad-debug.php 2 years ago ad-expiration.php 3 years ago ad-health-notices.php 2 years ago ad-model.php 2 years ago ad-select.php 3 years ago ad.php 2 years ago ad_ajax_callbacks.php 2 years ago ad_group.php 2 years ago ad_placements.php 2 years ago ad_type_abstract.php 2 years ago ad_type_content.php 2 years ago ad_type_dummy.php 2 years ago ad_type_group.php 2 years ago ad_type_image.php 2 years ago ad_type_plain.php 2 years ago checks.php 2 years ago class-translation-promo.php 2 years ago compatibility.php 2 years ago display-conditions.php 2 years ago filesystem.php 2 years ago frontend_checks.php 2 years ago in-content-injector.php 2 years ago inline-css.php 2 years ago plugin.php 2 years ago upgrades.php 2 years ago utils.php 3 years ago visitor-conditions.php 2 years ago widget.php 2 years ago
ad_placements.php
804 lines
1 <?php
2 // phpcs:ignoreFile
3 /**
4 * Advanced Ads
5 *
6 * @package Advanced_Ads_Placements
7 * @author Thomas Maier <support@wpadvancedads.com>
8 * @license GPL-2.0+
9 * @link https://wpadvancedads.com
10 * @copyright 2014 Thomas Maier, Advanced Ads GmbH
11 */
12
13 use AdvancedAds\Entities;
14 use AdvancedAds\Utilities\WordPress;
15
16 /**
17 * Grouping placements functions
18 *
19 * @since 1.1.0
20 * @package Advanced_Ads_Placements
21 * @author Thomas Maier <support@wpadvancedads.com>
22 */
23 class Advanced_Ads_Placements {
24
25 /**
26 * Gather placeholders which later are replaced by the ads
27 *
28 * @var array $ads_for_placeholders
29 */
30 private static $ads_for_placeholders = [];
31 /**
32 * Temporarily change content during processing
33 *
34 * @var array $placements
35 */
36 private static $replacements = [
37 'gcse:search' => 'gcse__search', // Google custom search namespaced tags.
38 ];
39
40 /**
41 * Return placement page description
42 *
43 * @deprecated 1.47.0
44 *
45 * @return string
46 */
47 public static function get_description() {
48 _deprecated_function( __METHOD__, '1.47.0', '\AdvancedAds\Entities::get_placement_description()' );
49 return Entities::get_placement_description();
50 }
51
52 /**
53 * Get placement types
54 *
55 * @return \Advanced_Ads\Placement_Type[] $types array with placement types
56 * @since 1.2.1
57 */
58 public static function get_placement_types() {
59 $types = [
60 'post_top' => [
61 'title' => __( 'Before Content', 'advanced-ads' ),
62 'description' => __( 'Injected before the post content.', 'advanced-ads' ),
63 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/content-before.png',
64 'order' => 20,
65 'options' => [
66 'show_position' => true,
67 'show_lazy_load' => true,
68 'uses_the_content' => true,
69 'amp' => true,
70 ],
71 ],
72 'post_content' => [
73 'title' => __( 'Content', 'advanced-ads' ),
74 'description' => __( 'Injected into the content. You can choose the paragraph after which the ad content is displayed.', 'advanced-ads' ),
75 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/content-within.png',
76 'order' => 21,
77 'options' => [
78 'show_position' => true,
79 'show_lazy_load' => true,
80 'uses_the_content' => true,
81 'amp' => true,
82 ],
83 ],
84 'post_bottom' => [
85 'title' => __( 'After Content', 'advanced-ads' ),
86 'description' => __( 'Injected after the post content.', 'advanced-ads' ),
87 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/content-after.png',
88 'order' => 35,
89 'options' => [
90 'show_position' => true,
91 'show_lazy_load' => true,
92 'uses_the_content' => true,
93 'amp' => true,
94 ],
95 ],
96 'sidebar_widget' => [
97 'title' => __( 'Sidebar Widget', 'advanced-ads' ),
98 'description' => __( 'Create a sidebar widget with an ad. Can be placed and used like any other widget.', 'advanced-ads' ),
99 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/widget.png',
100 'order' => 50,
101 'options' => [
102 'show_position' => true,
103 'show_lazy_load' => true,
104 'amp' => true,
105 ],
106 ],
107 'default' => [
108 'title' => __( 'Manual Placement', 'advanced-ads' ),
109 'description' => __( 'Manual placement to use as function or shortcode.', 'advanced-ads' ),
110 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/manual.png',
111 'order' => 80,
112 'options' => [
113 'show_position' => true,
114 'show_lazy_load' => true,
115 'amp' => true,
116 ],
117 ],
118 'header' => [
119 'title' => __( 'Header Code', 'advanced-ads' ),
120 'description' => __( 'Injected in Header (before closing &lt;/head&gt; Tag, often not visible).', 'advanced-ads' ),
121 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/header.png',
122 'order' => 3,
123 ],
124 'footer' => [
125 'title' => __( 'Footer Code', 'advanced-ads' ),
126 'description' => __( 'Injected in Footer (before closing &lt;/body&gt; Tag).', 'advanced-ads' ),
127 'image' => ADVADS_BASE_URL . 'admin/assets/img/placements/footer.png',
128 'order' => 95,
129 'options' => [ 'amp' => true ],
130 ],
131 ];
132
133 $types = (array) apply_filters( 'advanced-ads-placement-types', $types );
134
135 foreach ( $types as $type => $definition ) {
136 $types[ $type ] = new \Advanced_Ads\Placement_Type( $type, $definition );
137 }
138
139 return $types;
140 }
141
142 /**
143 * Update placements if sent
144 *
145 * @since 1.5.2
146 */
147 public static function update_placements() {
148
149 // check user permissions.
150 if ( ! WordPress::user_can( 'advanced_ads_manage_placements' ) ) {
151 return;
152 }
153
154 $success = null;
155
156 // add hook of last opened placement settings to URL.
157 $hook = ! empty( $_POST['advads-last-edited-placement'] ) ? '#single-placement-' . $_POST['advads-last-edited-placement'] : '';
158
159 if ( isset( $_POST['advads']['placement'] ) && check_admin_referer( 'advads-placement', 'advads_placement' ) ) {
160 $success = self::save_new_placement( $_POST['advads']['placement'] );
161 }
162 // save placement data.
163 if ( isset( $_POST['advads']['placements'] ) && check_admin_referer( 'advads-placement', 'advads_placement' ) ) {
164 $success = self::save_placements( $_POST['advads']['placements'] );
165 }
166
167 $success = apply_filters( 'advanced-ads-update-placements', $success );
168
169 if ( isset( $success ) ) {
170 $message = $success ? 'updated' : 'error';
171 wp_redirect( esc_url_raw( add_query_arg( [ 'message' => $message ] ) ) . $hook );
172 }
173 }
174
175 /**
176 * Save a new placement
177 *
178 * @param array $new_placement information about the new placement.
179 *
180 * @return mixed slug if saved; false if not
181 * @since 1.1.0
182 */
183 public static function save_new_placement( $new_placement ) {
184 // load placements // -TODO use model.
185 $placements = Advanced_Ads::get_ad_placements_array();
186
187 // create slug.
188 $new_placement['slug'] = sanitize_title( $new_placement['name'] );
189
190 if ( isset( $placements[ $new_placement['slug'] ] ) ) {
191 $i = 1;
192 // try to save placement until we found an empty slug.
193 do {
194 $i ++;
195 if ( 100 === $i ) { // prevent endless loop, just in case.
196 Advanced_Ads::log( 'endless loop when injecting placement' );
197 break;
198 }
199 } while ( isset( $placements[ $new_placement['slug'] . '_' . $i ] ) );
200
201 $new_placement['slug'] .= '_' . $i;
202 $new_placement['name'] .= ' ' . $i;
203 }
204
205 // check if slug already exists or is empty.
206 if ( '' === $new_placement['slug'] || isset( $placements[ $new_placement['slug'] ] ) || ! isset( $new_placement['type'] ) ) {
207 return false;
208 }
209
210 // make sure only allowed types are being saved.
211 $placement_types = self::get_placement_types();
212 $new_placement['type'] = ( isset( $placement_types[ $new_placement['type'] ] ) ) ? $new_placement['type'] : 'default';
213 // escape name.
214 $new_placement['name'] = esc_attr( $new_placement['name'] );
215
216 // add new place to all placements.
217 $placements[ $new_placement['slug'] ] = [
218 'type' => $new_placement['type'],
219 'name' => $new_placement['name'],
220 'item' => $new_placement['item'],
221 ];
222
223 // add index options.
224 if ( isset( $new_placement['options'] ) ) {
225 $placements[ $new_placement['slug'] ]['options'] = $new_placement['options'];
226 if ( isset( $placements[ $new_placement['slug'] ]['options']['index'] ) ) {
227 $placements[ $new_placement['slug'] ]['options']['index'] = absint( $placements[ $new_placement['slug'] ]['options']['index'] );
228 }
229 }
230
231 // save array.
232 Advanced_Ads::get_instance()->get_model()->update_ad_placements_array( $placements );
233
234 return $new_placement['slug'];
235 }
236
237 /**
238 * Save placements
239 *
240 * @param array $placement_items placements.
241 *
242 * @return mixed true if saved; error message if not
243 * @since 1.1.0
244 */
245 public static function save_placements( $placement_items ) {
246
247 // load placements // -TODO use model.
248 $placements = Advanced_Ads::get_ad_placements_array();
249
250 foreach ( $placement_items as $_placement_slug => $_placement ) {
251 // remove the placement.
252 if ( isset( $_placement['delete'] ) ) {
253 unset( $placements[ $_placement_slug ] );
254 continue;
255 }
256 // save item.
257 if ( isset( $_placement['item'] ) ) {
258 $placements[ $_placement_slug ]['item'] = $_placement['item'];
259 }
260 // save item options.
261 if ( isset( $_placement['options'] ) ) {
262 $placements[ $_placement_slug ]['options'] = $_placement['options'];
263 if ( isset( $placements[ $_placement_slug ]['options']['index'] ) ) {
264 $placements[ $_placement_slug ]['options']['index'] = absint( $placements[ $_placement_slug ]['options']['index'] );
265 }
266 } else {
267 $placements[ $_placement_slug ]['options'] = [];
268 }
269 }
270
271 // save array.
272 Advanced_Ads::get_instance()->get_model()->update_ad_placements_array( $placements );
273
274 return true;
275 }
276
277 /**
278 * Get items for item select field.
279 * Used for new placement form.
280 *
281 * @return array $select items for select field
282 */
283 public static function items_for_select() {
284 $select = [];
285 $model = Advanced_Ads::get_instance()->get_model();
286
287 // load all ad groups.
288 $groups = $model->get_ad_groups();
289 foreach ( $groups as $_group ) {
290 $select['groups'][ 'group_' . $_group->term_id ] = $_group->name;
291 }
292
293 // load all ads.
294 $ads = $model->get_ads(
295 [
296 'orderby' => 'title',
297 'order' => 'ASC',
298 ]
299 );
300 foreach ( $ads as $_ad ) {
301 $select['ads'][ 'ad_' . $_ad->ID ] = $_ad->post_title;
302 }
303
304 return $select;
305 }
306
307 /**
308 * Get html tags for content injection
309 *
310 * @return array $tags array with tags that can be used for content injection
311 * @since 1.3.5
312 */
313 public static function tags_for_content_injection() {
314 $headline_tags = apply_filters( 'advanced-ads-headlines-for-ad-injection', [ 'h2', 'h3', 'h4' ] );
315 $headline_tags_imploded = '&lt;' . implode( '&gt;, &lt;', $headline_tags ) . '&gt;';
316
317 $tags = apply_filters(
318 'advanced-ads-tags-for-injection',
319 [
320 // translators: %s is an html tag.
321 'p' => sprintf( __( 'paragraph (%s)', 'advanced-ads' ), '&lt;p&gt;' ),
322 // translators: %s is an html tag.
323 'pwithoutimg' => sprintf( __( 'paragraph without image (%s)', 'advanced-ads' ), '&lt;p&gt;' ),
324 // translators: %s is an html tag.
325 'h2' => sprintf( __( 'headline 2 (%s)', 'advanced-ads' ), '&lt;h2&gt;' ),
326 // translators: %s is an html tag.
327 'h3' => sprintf( __( 'headline 3 (%s)', 'advanced-ads' ), '&lt;h3&gt;' ),
328 // translators: %s is an html tag.
329 'h4' => sprintf( __( 'headline 4 (%s)', 'advanced-ads' ), '&lt;h4&gt;' ),
330 // translators: %s is an html tag.
331 'headlines' => sprintf( __( 'any headline (%s)', 'advanced-ads' ), $headline_tags_imploded ),
332 // translators: %s is an html tag.
333 'img' => sprintf( __( 'image (%s)', 'advanced-ads' ), '&lt;img&gt;' ),
334 // translators: %s is an html tag.
335 'table' => sprintf( __( 'table (%s)', 'advanced-ads' ), '&lt;table&gt;' ),
336 // translators: %s is an html tag.
337 'li' => sprintf( __( 'list item (%s)', 'advanced-ads' ), '&lt;li&gt;' ),
338 // translators: %s is an html tag.
339 'blockquote' => sprintf( __( 'quote (%s)', 'advanced-ads' ), '&lt;blockquote&gt;' ),
340 // translators: %s is an html tag.
341 'iframe' => sprintf( __( 'iframe (%s)', 'advanced-ads' ), '&lt;iframe&gt;' ),
342 // translators: %s is an html tag.
343 'div' => sprintf( __( 'container (%s)', 'advanced-ads' ), '&lt;div&gt;' ),
344 // any HTML tag.
345 'anyelement' => __( 'any element', 'advanced-ads' ),
346 // custom
347 'custom' => _x( 'custom', 'for the "custom" content placement option', 'advanced-ads' ),
348 ]
349 );
350
351 return $tags;
352 }
353
354 /**
355 * Return content of a placement
356 *
357 * @param string $id slug of the display.
358 * @param array $args optional arguments (passed to child).
359 *
360 * @return string
361 */
362 public static function output( $id = '', $args = [] ) {
363 // get placement data for the slug.
364 if ( '' == $id ) {
365 return;
366 }
367
368 $placements = Advanced_Ads::get_ad_placements_array();
369 $placement = ( isset( $placements[ $id ] ) && is_array( $placements[ $id ] ) ) ? $placements[ $id ] : [];
370
371 if ( isset( $args['change-placement'] ) ) {
372 // some options was provided by the user.
373 $placement = Advanced_Ads_Utils::merge_deep_array( [ $placement, $args['change-placement'] ] );
374 }
375
376 if ( isset( $placement['item'] ) && '' !== $placement['item'] ) {
377 $_item = explode( '_', $placement['item'] );
378
379 if ( ! isset( $_item[1] ) || empty( $_item[1] ) ) {
380 return;
381 }
382
383 // inject options.
384 if ( isset( $placement['options'] ) && is_array( $placement['options'] ) ) {
385 foreach ( $placement['options'] as $_k => $_v ) {
386 if ( ! isset( $args[ $_k ] ) ) {
387 $args[ $_k ] = $_v;
388 }
389 }
390 }
391
392 // inject placement type.
393 if ( isset( $placement['type'] ) ) {
394 $args['placement_type'] = $placement['type'];
395 }
396
397 // options.
398 $prefix = Advanced_Ads_Plugin::get_instance()->get_frontend_prefix();
399
400 // return either ad or group content.
401 switch ( $_item[0] ) {
402 case 'ad':
403 case Advanced_Ads_Select::AD:
404 // create class from placement id (not if header injection).
405 if ( ! isset( $placement['type'] ) || 'header' !== $placement['type'] ) {
406 if ( ! isset( $args['output'] ) ) {
407 $args['output'] = [];
408 }
409 if ( ! isset( $args['output']['class'] ) ) {
410 $args['output']['class'] = [];
411 }
412 $class = $prefix . $id;
413 if ( ! in_array( $class, $args['output']['class'] ) ) {
414 $args['output']['class'][] = $class;
415 }
416 }
417
418 // fix method id.
419 $_item[0] = Advanced_Ads_Select::AD;
420
421 /**
422 * Deliver the translated version of an ad if set up with WPML.
423 * If an ad is not translated, show the ad in the original language when this is the selected option in the WPML settings.
424 *
425 * @source https://wpml.org/wpml-hook/wpml_object_id/
426 * @source https://wpml.org/forums/topic/backend-custom-post-types-page-overview-with-translation-options/
427 *
428 */
429 if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
430 global $sitepress;
431 $_item[1] = apply_filters( 'wpml_object_id', $_item[1], 'advanced_ads', $sitepress->is_display_as_translated_post_type( 'advanced_ads' ) );
432 }
433 break;
434
435 case Advanced_Ads_Select::PLACEMENT:
436 // avoid loops (programmatical error).
437 return;
438
439 case Advanced_Ads_Select::GROUP:
440 $class = $prefix . $id;
441 if ( ( isset( $placement['type'] ) && $placement['type'] !== 'header' )
442 && ( ! isset( $args['output']['class'] )
443 || ! is_array( $args['output']['class'] )
444 || ! in_array( $class, $args['output']['class'] ) ) ) {
445 $args['output']['class'][] = $class;
446 }
447 default:
448 }
449
450 // create placement id for various features.
451 $args['output']['placement_id'] = $id;
452
453 // add the placement to the global output array.
454 $advads = Advanced_Ads::get_instance();
455 $name = isset( $placement['name'] ) ? $placement['name'] : $id;
456
457 $result = Advanced_Ads_Select::get_instance()->get_ad_by_method( (int) $_item[1], $_item[0], $args );
458
459 if ( $result && ( ! isset( $args['global_output'] ) || $args['global_output'] ) ) {
460 $advads->current_ads[] = [
461 'type' => 'placement',
462 'id' => $id,
463 'title' => $name,
464 ];
465 }
466
467 return $result;
468 }
469
470 return;
471 }
472
473 /**
474 * Inject ads directly into the content
475 *
476 * @param string $placement_id Id of the placement.
477 * @param array $placement_opts Placement options.
478 * @param string $content Content to inject placement into.
479 *
480 * @return string $content Content with injected placement.
481 * @since 1.2.1
482 */
483 public static function &inject_in_content( $placement_id, $placement_opts, &$content ) {
484 return Advanced_Ads_In_Content_Injector::inject_in_content( $placement_id, $placement_opts, $content );
485 }
486
487 /**
488 * Check if the placement can be displayed
489 *
490 * @param int $id placement id.
491 *
492 * @return bool true if placement can be displayed
493 * @since 1.6.9
494 */
495 public static function can_display( $id = 0 ) {
496 if ( ! isset( $id ) || 0 === $id ) {
497 return true;
498 }
499
500 return apply_filters( 'advanced-ads-can-display-placement', true, $id );
501 }
502
503 /**
504 * Get the placements that includes the ad or group.
505 *
506 * @param string $type 'ad' or 'group'.
507 * @param int $id Id.
508 *
509 * @return array
510 */
511 public static function get_placements_by( $type, $id ) {
512 $result = [];
513
514 $placements = Advanced_Ads::get_ad_placements_array();
515 foreach ( $placements as $_id => $_placement ) {
516 if ( isset( $_placement['item'] ) && $_placement['item'] === $type . '_' . $id ) {
517 $result[ $_id ] = $_placement;
518 }
519 }
520
521 return $result;
522 }
523
524 /**
525 * Get the markup for the group/ad selection for each placement.
526 *
527 * @param string $slug Slug for current placement. This is passed to the view.
528 * @param array $placement The current placement.
529 *
530 * @return string
531 */
532 public static function get_items_for_placement_markup( $slug, $placement ) {
533 $placement['item'] = $placement['item'] ?? '';
534 // Get the currently selected item.
535 $placement_item_array = explode( '_', $placement['item'] );
536 $placement_item_type = $placement_item_array[0];
537 $placement_item_id = (int) ( $placement_item_array[1] ?? 0 );
538
539 $items = self::get_items_for_placement( $placement['type'], $placement['item'] );
540
541 // check for missing items
542 if ( $placement_item_type && ! array_key_exists( $placement['item'], $items[ $placement_item_type . 's' ]['items'] ) ) {
543 $method = $placement_item_type === 'group' ? 'get_ad_groups' : 'get_ads';
544 $item = \Advanced_Ads::get_instance()->get_model()->{$method}( [ 'include' => $placement_item_id ] )[0] ?? null;
545
546 $items[ $placement_item_type . 's' ]['items'][ $placement['item'] ] = [
547 'selected' => true,
548 'disabled' => true,
549 ];
550
551 if ( $item instanceof WP_Post ) {
552 $items[ $placement_item_type . 's' ]['items'][ $placement['item'] ]['name'] = $item->post_title;
553 } elseif ( $item instanceof Advanced_Ads_Group ) {
554 $items[ $placement_item_type . 's' ]['items'][ $placement['item'] ]['name'] = $item->name;
555 } else {
556 unset( $items[ $placement_item_type . 's' ]['items'][ $placement['item'] ] );
557 }
558
559 if ( isset( $items[ $placement_item_type . 's' ]['items'][ $placement['item'] ] ) ) {
560 $items = array_map( static function( $items_group ) {
561 $keys = array_column( $items_group['items'], 'name' );
562 array_multisort( $keys, SORT_ASC, SORT_NATURAL, $items_group['items'] );
563
564 return $items_group;
565 }, $items );
566 }
567 }
568
569 $items = array_filter( $items, static function( $items_group ) {
570 return ! empty( $items_group['items'] );
571 } );
572
573 ob_start();
574
575 include ADVADS_ABSPATH . 'admin/views/placements-item.php';
576
577 return ob_get_clean();
578 }
579
580 /**
581 * Get the available items for the selected placement.
582 *
583 * @param string $type The current placement type.
584 * @param string $item The ad/group id.
585 *
586 * @return array[]
587 */
588 public static function get_items_for_placement( string $type, string $item = 'ad_0' ) : iterable {
589 $placement_type = self::get_placement_types()[ $type ];
590 $items = [
591 'groups' => [
592 'label' => __( 'Ad Groups', 'advanced-ads' ),
593 'items' => $placement_type->get_allowed_groups(),
594 ],
595 'ads' => [
596 'label' => __( 'Ads', 'advanced-ads' ),
597 'items' => $placement_type->get_allowed_ads(),
598 ],
599 ];
600
601 return array_map( static function( $items_group ) use ( $item ) {
602 array_walk( $items_group['items'], static function( &$value, $key ) use ( $item ) {
603 $value = [
604 'name' => $value,
605 'selected' => $key === $item,
606 'disabled' => false,
607 ];
608 } );
609
610 return $items_group;
611 }, $items );
612 }
613
614 /**
615 * Get paths of ancestors that should not contain ads.
616 *
617 * @param object $xpath DOMXPath object.
618 *
619 * @return array Paths of ancestors.
620 */
621 private static function get_ancestors_to_limit( $xpath ) {
622 $query = self::get_ancestors_to_limit_query();
623 if ( ! $query ) {
624 return [];
625 }
626
627 $node_list = $xpath->query( $query );
628 $ancestors_to_limit = [];
629
630 foreach ( $node_list as $a ) {
631 $ancestors_to_limit[] = $a->getNodePath();
632 }
633
634 return $ancestors_to_limit;
635 }
636
637
638 /**
639 * Remove paragraphs that has ancestors that should not contain ads.
640 *
641 * @param array $paragraphs An array of `DOMNode` objects to insert ads before or after.
642 * @param array $ancestors_to_limit Paths of ancestor that should not contain ads.
643 *
644 * @return array $new_paragraphs An array of `DOMNode` objects to insert ads before or after.
645 */
646 private static function filter_by_ancestors_to_limit( $paragraphs, $ancestors_to_limit ) {
647 $new_paragraphs = [];
648
649 foreach ( $paragraphs as $k => $paragraph ) {
650 foreach ( $ancestors_to_limit as $a ) {
651 if ( 0 === stripos( $paragraph->getNodePath(), $a ) ) {
652 continue 2;
653 }
654 }
655
656 $new_paragraphs[] = $paragraph;
657 }
658
659 return $new_paragraphs;
660 }
661
662 /**
663 * Get query to select ancestors that should not contain ads.
664 *
665 * @return string/false DOMXPath query or false.
666 */
667 private static function get_ancestors_to_limit_query() {
668 /**
669 * TODO:
670 * - support `%` (rand) at the start
671 * - support plain text that node should contain instead of CSS selectors
672 * - support `prev` and `next` as `type`
673 */
674
675 /**
676 * Filter the nodes that limit injection.
677 *
678 * @param array An array of arrays, each of which contains:
679 *
680 * @type string $type Accept: `ancestor` - limit injection inside the ancestor.
681 * @type string $node A "class selector" which targets one class (.) or "id selector" which targets one id (#),
682 * optionally with `%` at the end.
683 */
684 $items = apply_filters(
685 'advanced-ads-content-injection-nodes-without-ads',
686 [
687 [
688 // a class anyone can use to prevent automatic ad injection into a specific element.
689 'node' => '.advads-stop-injection',
690 'type' => 'ancestor',
691 ],
692 [
693 // Product Slider for Beaver Builder by WooPack.
694 'node' => '.woopack-product-carousel',
695 'type' => 'ancestor',
696 ],
697 [
698 // WP Author Box Lite.
699 'node' => '#wpautbox-%',
700 'type' => 'ancestor',
701 ],
702 [
703 // GeoDirectory Post Slider.
704 'node' => '.geodir-post-slider',
705 'type' => 'ancestor',
706 ],
707 ]
708 );
709
710 $query = [];
711 foreach ( $items as $p ) {
712 $sel = $p['node'];
713
714 $sel_type = substr( $sel, 0, 1 );
715 $sel = substr( $sel, 1 );
716
717 $rand_pos = strpos( $sel, '%' );
718 $sel = str_replace( '%', '', $sel );
719 $sel = sanitize_html_class( $sel );
720
721 if ( '.' === $sel_type ) {
722 if ( false !== $rand_pos ) {
723 $query[] = "@class and contains(concat(' ', normalize-space(@class), ' '), ' $sel')";
724 } else {
725 $query[] = "@class and contains(concat(' ', normalize-space(@class), ' '), ' $sel ')";
726 }
727 }
728 if ( '#' === $sel_type ) {
729 if ( false !== $rand_pos ) {
730 $query[] = "@id and starts-with(@id, '$sel')";
731 } else {
732 $query[] = "@id and @id = '$sel'";
733 }
734 }
735 }
736
737 if ( ! $query ) {
738 return false;
739 }
740
741 return '//*[' . implode( ' or ', $query ) . ']';
742 }
743
744 /**
745 * Sort placements
746 *
747 * @param array $placements Existing placements.
748 * @param string $orderby The field to order by. Accept `name` or `type`.
749 * @return array $placements Sorted placements.
750 */
751 public static function sort( $placements, $orderby = 'name' ) {
752 if ( ! is_array( $placements ) ) {
753 return [];
754 }
755 if ( 'name' === $orderby ) {
756 ksort( $placements, SORT_NATURAL );
757 return $placements;
758 }
759 uasort( $placements, [ 'Advanced_Ads_Placements', 'sort_by_type_callback' ] );
760 return $placements;
761
762 }
763
764 /**
765 * Callback to sort placements by type.
766 *
767 * @param array $f First placement.
768 * @param array $s Second placement.
769 * @return int 0 If placements are equal, -1 if the first should come first, 1 otherwise.
770 */
771 private static function sort_by_type_callback( $f, $s ) {
772 // A placement with the "Words Between Ads" option set to non-zero gets injected after others
773 // because it reads existing ads.
774 if ( ! empty( $f['options']['words_between_repeats'] ) xor ! empty( $s['options']['words_between_repeats'] ) ) {
775 return ! empty( $f['options']['words_between_repeats'] ) ? 1 : -1;
776 }
777
778 $types = self::get_placement_types();
779
780 $f_o = ( isset( $f['type'] ) && isset( $types[ $f['type'] ]['order'] ) ) ? $types[ $f['type'] ]['order'] : 100;
781 $s_o = ( isset( $s['type'] ) && isset( $types[ $s['type'] ]['order'] ) ) ? $types[ $s['type'] ]['order'] : 100;
782
783 if ( $f_o === $s_o ) {
784 // Sort by index.
785 if ( 'post_content' === $f['type'] && isset( $f['options']['index'] ) && isset( $s['options']['index'] )
786 && $f['options']['index'] !== $s['options']['index'] ) {
787 return ( $f['options']['index'] < $s['options']['index'] ) ? -1 : 1;
788 }
789
790 // Sort by name.
791 if ( isset( $f['name'] ) && isset( $s['name'] ) ) {
792 return 0 > strnatcmp( $f['name'], $s['name'] ) ? -1 : 1;
793 }
794 return 0;
795 }
796
797 // Sort by order.
798 return ( $f_o < $s_o ) ? -1 : 1;
799
800 }
801
802
803 }
804