PluginProbe ʕ •ᴥ•ʔ
Advanced Ads – Ad Manager & AdSense / 2.0.25
Advanced Ads – Ad Manager & AdSense v2.0.25
2.0.25 2.0.24 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 / includes / placements / class-placement-repository.php
advanced-ads / includes / placements Last commit date
types 3 months ago class-placement-after-content.php 1 year ago class-placement-before-content.php 1 year ago class-placement-content.php 1 year ago class-placement-factory.php 2 days ago class-placement-footer.php 1 year ago class-placement-header.php 1 year ago class-placement-repository.php 2 days ago class-placement-sidebar-widget.php 1 year ago class-placement-standard.php 1 year ago class-placement-types.php 2 days ago class-placements.php 2 days ago index.php 1 year ago
class-placement-repository.php
435 lines
1 <?php
2 /**
3 * The placement repository.
4 *
5 * @package AdvancedAds
6 * @author Advanced Ads <info@wpadvancedads.com>
7 * @since 1.50.0
8 */
9
10 namespace AdvancedAds\Placements;
11
12 use AdvancedAds\Abstracts\Placement;
13 use AdvancedAds\Constants;
14 use AdvancedAds\Cache_Invalidator;
15 use AdvancedAds\Traits\Repository_Helpers;
16 use AdvancedAds\Utilities\Cache;
17 use AdvancedAds\Utilities\WordPress;
18 use Exception;
19 use WP_Query;
20
21 defined( 'ABSPATH' ) || exit;
22
23 /**
24 * Placements Repository.
25 */
26 class Placement_Repository {
27 use Repository_Helpers;
28
29 /* CRUD Methods ------------------- */
30
31 /**
32 * Create a new placement in the database.
33 *
34 * @param Placement $placement Placement object.
35 *
36 * @return Placement
37 */
38 public function create( &$placement ): Placement {
39 $id = wp_insert_post(
40 apply_filters(
41 'advanced-ads-new-placement-data',
42 [
43 'post_type' => Constants::POST_TYPE_PLACEMENT,
44 'post_name' => $placement->get_slug(),
45 'post_status' => $placement->get_status() ? $placement->get_status() : 'publish',
46 'post_author' => get_current_user_id(),
47 'post_title' => $placement->get_title() ? $placement->get_title() : __( 'New Placement', 'advanced-ads' ),
48 'post_content' => $placement->get_content() ? $placement->get_content() : __( 'New placement content goes here', 'advanced-ads' ),
49 'comment_status' => 'closed',
50 'ping_status' => 'closed',
51 ]
52 ),
53 true
54 );
55
56 if ( $id && ! is_wp_error( $id ) ) {
57 $placement->set_id( $id );
58 $this->update_post_meta( $placement );
59 $placement->apply_changes();
60
61 Cache_Invalidator::invalidate_placements();
62 }
63
64 return $placement;
65 }
66
67 /**
68 * Read a placement from the database.
69 *
70 * @param Placement $placement Placement object.
71 *
72 * @throws Exception If invalid placement.
73 *
74 * @return void
75 */
76 public function read( &$placement ): void {
77 $placement->set_defaults();
78 $post_object = get_post( $placement->get_id() );
79
80 if ( ! $placement->get_id() || ! $post_object || Constants::POST_TYPE_PLACEMENT !== $post_object->post_type ) {
81 throw new Exception( esc_html__( 'Invalid placement.', 'advanced-ads' ) );
82 }
83
84 $placement->set_props(
85 [
86 'title' => $post_object->post_title,
87 'slug' => $post_object->post_name,
88 'status' => $post_object->post_status,
89 'content' => $post_object->post_content,
90 ]
91 );
92
93 $this->read_placement_data( $placement );
94 $placement->set_object_read( true );
95 }
96
97 /**
98 * Update an existing placement in the database.
99 *
100 * @param Placement $placement Placement object.
101 *
102 * @return void
103 */
104 public function update( &$placement ): void {
105 global $wpdb;
106
107 $changes = $placement->get_changes();
108
109 // Only update the post when the post data changes.
110 if ( array_intersect( [ 'title', 'status', 'content' ], array_keys( $changes ) ) ) {
111 $post_data = [
112 'post_title' => $placement->get_title( 'edit' ),
113 'post_status' => $placement->get_status( 'edit' ) ? $placement->get_status( 'edit' ) : 'publish',
114 'post_type' => Constants::POST_TYPE_PLACEMENT,
115 'post_content' => wp_unslash(
116 apply_filters(
117 'content_save_pre',
118 $placement->get_content( 'edit' )
119 )
120 ),
121 ];
122
123 /**
124 * When updating this object, to prevent infinite loops, use $wpdb
125 * to update data, since wp_update_post spawns more calls to the
126 * save_post action.
127 *
128 * This ensures hooks are fired by either WP itself (admin screen save),
129 * or an update purely from CRUD.
130 */
131 if ( doing_action( 'save_post' ) ) {
132 $GLOBALS['wpdb']->update( $GLOBALS['wpdb']->posts, $post_data, [ 'ID' => $placement->get_id() ] );
133 clean_post_cache( $placement->get_id() );
134 } else {
135 wp_update_post( array_merge( [ 'ID' => $placement->get_id() ], $post_data ) );
136 }
137 } else { // Only update post modified time to record this save event.
138 $this->touch_post_modified_time( $placement->get_id() );
139 }
140
141 $this->update_post_meta( $placement );
142
143 $placement->apply_changes();
144
145 Cache_Invalidator::invalidate_placements();
146 }
147
148 /**
149 * Delete an placement from the database.
150 *
151 * @param Placement $placement Placement object or id.
152 * @param bool $force_delete Whether to bypass Trash and force deletion. Default false.
153 *
154 * @return void
155 */
156 public function delete( &$placement, $force_delete = false ): void {
157 // Early bail!!
158 if ( ! $placement || ! $placement->get_id() ) {
159 return;
160 }
161
162 if ( $force_delete ) {
163 wp_delete_post( $placement->get_id(), true );
164 $placement->set_id( 0 );
165 } else {
166 wp_trash_post( $placement->get_id() );
167 $placement->set_status( 'trash' );
168 }
169
170 Cache_Invalidator::invalidate_placements();
171 }
172
173 /* Finder Methods ------------------- */
174
175 /**
176 * Find placement by item id.
177 *
178 * Example: Find all placements that are connected to a specific ad.
179 * In this case $item_id = "ad_1234"
180 *
181 * Example: Find all placements that are connected to a specific group.
182 * In this case $item_id = "group_1234"
183 *
184 * @param string $item_id Item id to search for.
185 *
186 * @return Placement[]
187 */
188 public function find_by_item_id( $item_id ): array {
189 if ( ! is_string( $item_id ) || '' === $item_id ) {
190 return [];
191 }
192
193 $placement_ids = [];
194
195 foreach ( $this->get_placement_summaries() as $id => $summary ) {
196 if ( $item_id === $summary['item'] ) {
197 $placement_ids[] = (int) $id;
198 }
199 }
200
201 return $this->hydrate_placements( $placement_ids );
202 }
203
204 /**
205 * Retrieves placements by type.
206 *
207 * Filters cached placement summaries by type and hydrates matches.
208 *
209 * @param string|array $types Placement types to query.
210 * @param string $output The required return type. One of OBJECT or ids,
211 * which correspond to an Placement object or an array containing post ids respectively.
212 * @param bool $published_only Whether to return only published placements.
213 *
214 * @return array An associative array of placement IDs as keys and their corresponding placement objects as values.
215 */
216 public function find_by_types( $types, $output = OBJECT, $published_only = false ): array {
217 $types = array_values( array_filter( array_map( 'sanitize_key', (array) $types ) ) );
218
219 if ( empty( $types ) ) {
220 return [];
221 }
222
223 $placement_ids = [];
224
225 foreach ( $this->get_placement_summaries() as $id => $summary ) {
226 if ( $published_only && 'publish' !== $summary['status'] ) {
227 continue;
228 }
229
230 if ( in_array( $summary['type'], $types, true ) ) {
231 $placement_ids[] = (int) $id;
232 }
233 }
234
235 if ( 'ids' === $output ) {
236 return $placement_ids;
237 }
238
239 return $this->hydrate_placements( $placement_ids );
240 }
241
242 /**
243 * Get array of all placements.
244 *
245 * Prefer get_placement_summaries() for admin lists that only need id, title, type,
246 * status, or author — avoids constructing a full Placement instance per row.
247 *
248 * @return Placement[]
249 */
250 public function get_all_placements(): array {
251 return $this->hydrate_placements( array_keys( $this->get_placement_summaries() ) );
252 }
253
254 /**
255 * Get array of all published placements.
256 *
257 * @return Placement[]
258 */
259 public function get_all_published(): array {
260 return $this->hydrate_placements( $this->get_published_ids() );
261 }
262
263 /**
264 * Get lightweight placement summaries for list UIs (cached cross-request).
265 *
266 * @return array<int, array{id: int, title: string, type: string, status: string, author_id: int, item: string}>
267 */
268 public function get_placement_summaries(): array {
269 $summaries = Cache::get( Cache::PREFIX_PLACEMENTS, Cache::KEY_SUMMARIES );
270
271 if ( null === $summaries ) {
272 $summaries = $this->query_placement_summaries();
273 Cache::set( Cache::PREFIX_PLACEMENTS, Cache::KEY_SUMMARIES, $summaries );
274 }
275
276 return $summaries;
277 }
278
279 /**
280 * Get all placement as ID => title, derived from cached summaries.
281 *
282 * @return array<int, string>
283 */
284 public function get_placements_dropdown(): array {
285 return $this->summaries_to_dropdown( $this->get_placement_summaries() );
286 }
287
288 /**
289 * Get array of all published placement IDs, derived from cached summaries.
290 *
291 * @return int[]
292 */
293 public function get_published_ids(): array {
294 $published_ids = [];
295
296 foreach ( $this->get_placement_summaries() as $id => $summary ) {
297 if ( 'publish' === $summary['status'] ) {
298 $published_ids[] = (int) $id;
299 }
300 }
301
302 return $published_ids;
303 }
304
305 /**
306 * Query lightweight placement summaries without hydrating Placement objects.
307 *
308 * @return array<int, array{id: int, title: string, type: string, status: string, author_id: int, item: string}>
309 */
310 private function query_placement_summaries(): array {
311 $query = new WP_Query(
312 WordPress::improve_wp_query(
313 [
314 'post_type' => Constants::POST_TYPE_PLACEMENT,
315 'posts_per_page' => -1,
316 'post_status' => 'any',
317 'orderby' => 'title',
318 'order' => 'ASC',
319 'suppress_filters' => defined( 'ICL_SITEPRESS_VERSION' ) ? true : false, // Suppress filters if WPML is present.
320 ]
321 )
322 );
323
324 if ( ! $query->have_posts() ) {
325 return [];
326 }
327
328 $post_ids = wp_list_pluck( $query->posts, 'ID' );
329 _prime_post_caches( $post_ids, false, true );
330 update_meta_cache( 'post', $post_ids );
331
332 $summaries = [];
333 foreach ( $query->posts as $post ) {
334 $type = get_post_meta( $post->ID, 'type', true );
335 $item = get_post_meta( $post->ID, 'item', true );
336
337 $summaries[ (int) $post->ID ] = [
338 'id' => (int) $post->ID,
339 'title' => $post->post_title,
340 'type' => $type ? $type : 'default',
341 'status' => $post->post_status,
342 'author_id' => (int) $post->post_author,
343 'item' => is_string( $item ) ? $item : '',
344 ];
345 }
346
347 return $summaries;
348 }
349
350 /**
351 * Hydrate placement objects for the given post IDs.
352 *
353 * @param int[] $post_ids Placement post IDs.
354 *
355 * @return Placement[]
356 */
357 private function hydrate_placements( array $post_ids ): array {
358 return $this->hydrate_post_entities( $post_ids, 'wp_advads_get_placement', true );
359 }
360
361 /* Additional Methods ------------------- */
362
363 /**
364 * Read placement data. Can be overridden by child classes to load other props.
365 *
366 * @param Ad $placement Ad object.
367 *
368 * @return void
369 */
370 private function read_placement_data( &$placement ): void {
371 $item = get_post_meta( $placement->get_id(), 'item', true );
372 $options = get_post_meta( $placement->get_id(), 'options', true );
373 $type = get_post_meta( $placement->get_id(), 'type', true );
374
375 if ( empty( $options ) || ! is_array( $options ) ) {
376 $options = [];
377 }
378
379 $display_conditions = $options['display'] ?? [];
380 $visitor_conditions = $options['visitors'] ?? [];
381 unset( $options['display'], $options['visitors'] );
382
383 $placement->set_item( $item );
384 $placement->set_type( $type ?? 'default' );
385 $placement->set_props( $options );
386 $placement->set_display_conditions( $display_conditions );
387 $placement->set_visitor_conditions( $visitor_conditions );
388 }
389
390 /**
391 * Update placement data. Can be overridden by child classes to load other props.
392 *
393 * @param Placement $placement Placement object.
394 *
395 * @return void
396 */
397 private function update_post_meta( &$placement ): void {
398 do_action( 'advanced-ads-placement-pre-save', $placement );
399
400 $meta_keys = $placement->get_data_keys();
401 $meta_keys = array_combine( $meta_keys, $meta_keys );
402
403 $meta_values = [];
404 foreach ( $meta_keys as $meta_key => $prop ) {
405 $value = method_exists( $placement, "get_$prop" )
406 ? $placement->{"get_$prop"}( 'edit' )
407 : $placement->get_prop( $prop, 'edit' );
408
409 $value = is_string( $value ) ? wp_slash( $value ) : $value;
410
411 switch ( $prop ) {
412 case 'display':
413 case 'visitors':
414 $value = WordPress::sanitize_conditions( $value );
415 break;
416 }
417
418 $meta_values[ $meta_key ] = $value;
419 }
420
421 $meta_values = array_diff_key(
422 $meta_values,
423 [
424 'type' => true,
425 'slug' => true,
426 'item' => true,
427 ]
428 );
429
430 update_post_meta( $placement->get_id(), 'item', $placement->get_item() );
431 update_post_meta( $placement->get_id(), 'type', $placement->get_type() );
432 update_post_meta( $placement->get_id(), 'options', $meta_values );
433 }
434 }
435