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