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