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 / admin / ads / class-quick-bulk-edit.php
advanced-ads / includes / admin / ads Last commit date
class-list-table.php 3 weeks ago class-quick-bulk-edit.php 3 weeks ago
class-quick-bulk-edit.php
400 lines
1 <?php
2 /**
3 * Add quick/bulk edit fields on the ad overview page
4 *
5 * @package AdvancedAds
6 * @author Advanced Ads <info@wpadvancedads.com>
7 * @since 2.0
8 */
9
10 namespace AdvancedAds\Admin\Ads;
11
12 use AdvancedAds\Abstracts\Ad;
13 use AdvancedAds\Constants;
14 use AdvancedAds\Framework\Utilities\Params;
15 use AdvancedAds\Options;
16 use AdvancedAds\Utilities\WordPress;
17 use DateTimeImmutable;
18 use DateTimeZone;
19 use Exception;
20
21 defined( 'ABSPATH' ) || exit;
22
23 /**
24 * WP integration
25 */
26 class Quick_Bulk_Edit {
27 /**
28 * Whether bulk edit already ran this request.
29 *
30 * Core bulk edit fires save_post once per selected post; without this guard
31 * every firing would re-save the entire selection (N²).
32 *
33 * @var bool
34 */
35 private static $bulk_edit_ran = false;
36
37 /**
38 * Hooks into WordPress
39 *
40 * @return void
41 */
42 public function hooks(): void {
43 add_action( 'quick_edit_custom_box', [ $this, 'add_quick_edit_fields' ], 10, 2 );
44 add_action( 'bulk_edit_custom_box', [ $this, 'add_bulk_edit_fields' ], 10, 2 );
45 add_action( 'save_post', [ $this, 'save_quick_edits' ], 100 );
46 add_action( 'save_post', [ $this, 'save_bulk_edit' ], 100 );
47 add_action( 'advanced-ads-ad-render-column-ad_type', [ $this, 'print_ad_json' ] );
48 }
49
50 /**
51 * Print ad JSON for the quick-edit JS form filler.
52 *
53 * @param Ad $ad the ad being saved.
54 *
55 * @return void
56 */
57 public function print_ad_json( $ad ): void {
58 ?>
59 <script type="text/javascript">
60 var ad_json_<?php echo esc_attr( $ad->get_id() ); ?> = <?php echo wp_json_encode( $this->get_json_data( $ad ) ); ?>;
61 </script>
62 <?php
63 }
64
65 /**
66 * Save changes made during bulk edit
67 *
68 * @return void
69 */
70 public function save_bulk_edit(): void {
71 // Not bulk edit, not ads or not enough permissions.
72 if (
73 ! wp_verify_nonce( sanitize_key( Params::get( '_wpnonce', '', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ), 'bulk-posts' )
74 || Constants::POST_TYPE_AD !== sanitize_key( Params::get( 'post_type' ) )
75 || ! current_user_can( 'advanced_ads_edit_ads' )
76 ) {
77 return;
78 }
79
80 $flags = [ 'on', 'off' ];
81
82 $debug_mode = Params::get( 'debug_mode' );
83 $set_expiry = Params::get( 'expiry_date' );
84 $ad_label = Params::get( 'ad_label', false );
85 $ignore_privacy = Params::get( 'ignore_privacy' );
86
87 $has_change = in_array( $debug_mode, $flags, true )
88 || in_array( $set_expiry, $flags, true )
89 || in_array( $ignore_privacy, $flags, true )
90 || false !== $ad_label;
91
92 /**
93 * Allow add-ons to confirm early abort if no change has been made and avoid iterating through an ad stack.
94 *
95 * @param bool $has_change whether some ads have been changed.
96 */
97 $has_change = apply_filters( 'advanced-ads-bulk-edit-has-change', $has_change );
98
99 // No changes, bail out.
100 if ( ! $has_change ) {
101 return;
102 }
103
104 $ads = array_map(
105 static function ( $ad ) {
106 return wp_advads_get_ad( absint( $ad ) );
107 },
108 wp_unslash( Params::get( 'post', [], FILTER_DEFAULT, FILTER_REQUIRE_ARRAY ) )
109 );
110
111 $this->persist_bulk_edit(
112 $ads,
113 [
114 'debug_mode' => $debug_mode,
115 'set_expiry' => $set_expiry,
116 'expiry_date' => 'on' === $set_expiry ? $this->get_expiry_timestamp( 'get' ) : 0,
117 'ad_label' => $ad_label,
118 'ignore_privacy' => $ignore_privacy,
119 ]
120 );
121 }
122
123 /**
124 * Persist bulk-edit field changes once per request.
125 *
126 * @param array<int, \AdvancedAds\Abstracts\Ad> $ads Ad instances.
127 * @param array<string, mixed> $changes Parsed change payload.
128 *
129 * @return void
130 */
131 private function persist_bulk_edit( array $ads, array $changes ): void {
132 if ( self::$bulk_edit_ran ) {
133 return;
134 }
135 self::$bulk_edit_ran = true;
136
137 $flags = [ 'on', 'off' ];
138 $debug_mode = $changes['debug_mode'] ?? null;
139 $set_expiry = $changes['set_expiry'] ?? null;
140 $expiry_date = (int) ( $changes['expiry_date'] ?? 0 );
141 $ad_label = $changes['ad_label'] ?? false;
142 $ignore_privacy = $changes['ignore_privacy'] ?? null;
143
144 foreach ( $ads as $ad ) {
145 if ( ! $ad ) {
146 continue;
147 }
148
149 if ( in_array( $debug_mode, $flags, true ) ) {
150 $ad->set_debugmode( 'on' === $debug_mode );
151 }
152
153 if ( in_array( $set_expiry, $flags, true ) ) {
154 $ad->set_prop( 'expiry_date', $expiry_date );
155 }
156
157 if ( false !== $ad_label ) {
158 $ad->set_prop( 'ad_label', sanitize_text_field( wp_unslash( $ad_label ) ) );
159 }
160
161 if ( 'on' === $ignore_privacy ) {
162 $ad->set_prop( 'privacy', [ 'ignore-consent' => 'on' ] );
163 } elseif ( 'off' === $ignore_privacy ) {
164 $ad->unset_prop( 'privacy' );
165 }
166
167 /**
168 * Allow add-on to bulk save ads.
169 *
170 * @param Ad $ad current ad being saved.
171 */
172 $ad = apply_filters( 'advanced-ads-bulk-edit-save', $ad );
173
174 $ad->save();
175 }
176 }
177
178 /**
179 * Save ad edited with quick edit
180 *
181 * @param int $id the ad being saved.
182 *
183 * @return void
184 */
185 public function save_quick_edits( $id ): void {
186 // Not inline edit, or no permission.
187 if (
188 ! wp_verify_nonce( sanitize_key( Params::post( '_inline_edit' ) ), 'inlineeditnonce' ) ||
189 ! current_user_can( 'advanced_ads_edit_ads' )
190 ) {
191 return;
192 }
193
194 $ad = wp_advads_get_ad( $id );
195
196 // Not an ad.
197 if ( ! $ad ) {
198 return;
199 }
200
201 // Re-register columns for the AJAX list-table response.
202 ( new List_Table() )->hooks();
203
204 $ad->set_debugmode( Params::post( 'debugmode', false, FILTER_VALIDATE_BOOLEAN ) );
205 $ad->set_prop(
206 'expiry_date',
207 Params::post( 'enable_expiry' ) ? $this->get_expiry_timestamp() : 0
208 );
209
210 if ( Options::instance()->get( 'privacy.enabled' ) ) {
211 if ( Params::post( 'ignore_privacy' ) ) {
212 $ad->set_prop( 'privacy', [ 'ignore-consent' => 'on' ] );
213 } else {
214 $ad->unset_prop( 'privacy' );
215 }
216 }
217
218 $ad_label = Params::post( 'ad_label', false );
219 if ( false !== $ad_label ) {
220 $ad->set_prop( 'ad_label', sanitize_text_field( wp_unslash( $ad_label ) ) );
221 }
222
223 /**
224 * Allow add-ons to edit and ad before it is saved.
225 *
226 * @param Ad $ad the ad being saved.
227 */
228 $ad = apply_filters( 'advanced-ads-quick-edit-save', $ad );
229
230 $ad->save();
231 }
232
233 /**
234 * Get Unix timestamp from the date time inputs values
235 *
236 * @param string $method method used for the form - `post` or `get`.
237 *
238 * @return int
239 */
240 private function get_expiry_timestamp( $method = 'post' ): int {
241 $day = absint( 'get' === $method ? Params::get( 'day' ) : Params::post( 'day' ) );
242 $month = absint( 'get' === $method ? Params::get( 'month' ) : Params::post( 'month' ) );
243 $year = 'get' === $method ? Params::get( 'year', 0, FILTER_VALIDATE_INT ) : Params::post( 'year', 0, FILTER_VALIDATE_INT );
244 $hours = absint( 'get' === $method ? Params::get( 'hour' ) : Params::post( 'hour' ) );
245 $minutes = absint( 'get' === $method ? Params::get( 'minute' ) : Params::post( 'minute' ) );
246
247 try {
248 $local_dt = new DateTimeImmutable( 'now', WordPress::get_timezone() );
249 $local_dt = $local_dt->setDate( $year, $month, $day )->setTime( $hours, $minutes );
250
251 return $local_dt->getTimestamp();
252 } catch ( Exception $e ) {
253 return 0;
254 }
255 }
256
257 /**
258 * Add the bulk edit inputs
259 *
260 * @param string $column_name the current column.
261 * @param string $post_type the current post type.
262 *
263 * @return void
264 */
265 public function add_bulk_edit_fields( $column_name, $post_type ): void {
266 if ( Constants::POST_TYPE_AD !== $post_type || 'ad_type' !== $column_name ) {
267 return;
268 }
269
270 $is_privacy_enabled = (bool) Options::instance()->get( 'privacy.enabled' );
271 include ADVADS_ABSPATH . 'views/admin/bulk-edit.php';
272
273 /**
274 * Allow add-ons to add more fields.
275 */
276 do_action( 'advanced-ads-bulk-edit-fields' );
277 }
278
279 /**
280 * Add the quick edit inputs
281 *
282 * @param string $column_name the current column.
283 * @param string $post_type the current post type.
284 *
285 * @return void
286 */
287 public function add_quick_edit_fields( $column_name, $post_type ): void {
288 if ( Constants::POST_TYPE_AD !== $post_type || 'ad_date' !== $column_name ) {
289 return;
290 }
291
292 $is_privacy_enabled = (bool) Options::instance()->get( 'privacy.enabled' );
293 include ADVADS_ABSPATH . 'views/admin/quick-edit.php';
294
295 /**
296 * Allow add-ons to add more fields.
297 */
298 do_action( 'advanced-ads-quick-edit-fields' );
299 }
300
301 /**
302 * Print date and time inputs for the ad expiry
303 *
304 * @param int $timestamp default expiry date.
305 * @param string $prefix prefix for input names.
306 *
307 * @return void
308 */
309 public static function print_date_time_inputs( $timestamp = 0, $prefix = '' ): void {
310 global $wp_locale;
311
312 try {
313 $initial_date = $timestamp ? new DateTimeImmutable( "@$timestamp", new DateTimeZone( 'UTC' ) ) : current_datetime();
314 } catch ( Exception $e ) {
315 $initial_date = current_datetime();
316 }
317
318 $current_year = (int) current_datetime()->format( 'Y' );
319 $expiry_year = (int) $initial_date->format( 'Y' );
320 $start_year = min( $current_year, $expiry_year );
321 $end_year = max( $current_year + 10, $expiry_year );
322
323 ?>
324 <label>
325 <span class="screen-reader-text"><?php esc_html_e( 'Month', 'advanced-ads' ); ?></span>
326 <select name="<?php echo esc_attr( $prefix ); ?>month">
327 <?php for ( $mo = 1; $mo < 13; $mo++ ) : ?>
328 <?php $month = zeroise( $mo, 2 ); ?>
329 <option value="<?php echo esc_attr( $month ); ?>" <?php selected( $month, $initial_date->format( 'm' ) ); ?>>
330 <?php echo esc_html( $month . '-' . $wp_locale->get_month_abbrev( $wp_locale->get_month( $mo, 2 ) ) ); ?>
331 </option>
332 <?php endfor; ?>
333 </select>
334 </label>
335 <label>
336 <span class="screen-reader-text"><?php esc_html_e( 'Day', 'advanced-ads' ); ?></span>
337 <input type="number" name="<?php echo esc_attr( $prefix ); ?>day" min="1" max="31" value="<?php echo esc_attr( $initial_date->format( 'd' ) ); ?>"/>
338 </label>,
339 <label>
340 <span class="screen-reader-text"><?php esc_html_e( 'Year', 'advanced-ads' ); ?></span>
341 <select name="<?php echo esc_attr( $prefix ); ?>year">
342 <?php for ( $y = $start_year; $y <= $end_year; $y++ ) : ?>
343 <option value="<?php echo esc_attr( $y ); ?>" <?php selected( $y, $expiry_year ); ?>><?php echo esc_html( $y ); ?></option>
344 <?php endfor; ?>
345 </select>
346 </label>
347 @
348 <label>
349 <span class="screen-reader-text"><?php esc_html_e( 'Hour', 'advanced-ads' ); ?></span>
350 <input type="number" name="<?php echo esc_attr( $prefix ); ?>hour" min="0" max="23" value="<?php echo esc_attr( $initial_date->format( 'H' ) ); ?>"/>
351 </label>:
352 <label>
353 <span class="screen-reader-text"><?php esc_html_e( 'Minute', 'advanced-ads' ); ?></span>
354 <input type="number" name="<?php echo esc_attr( $prefix ); ?>minute" min="0" max="59" value="<?php echo esc_attr( $initial_date->format( 'i' ) ); ?>"/>
355 </label>
356 <?php
357 $timezone = wp_timezone_string();
358 echo esc_html( false !== strpbrk( $timezone, '+-' ) ? "UTC{$timezone}" : $timezone );
359 }
360
361 /**
362 * Get ad data for json output
363 *
364 * @param Ad $ad Ad instance.
365 *
366 * @return array<string, mixed>
367 */
368 private function get_json_data( $ad ): array {
369 $expiry = $ad->get_expiry_date();
370
371 $ad_data = [
372 'debug_mode' => $ad->is_debug_mode(),
373 'expiry' => $expiry
374 ? [
375 'expires' => true,
376 'expiry_date' => array_combine(
377 [ 'year', 'month', 'day', 'hour', 'minute' ],
378 explode( '-', wp_date( 'Y-m-d-H-i', $expiry ) )
379 ),
380 ]
381 : [
382 'expires' => false,
383 ],
384 'ad_label' => $ad->get_prop( 'ad_label' ),
385 ];
386
387 if ( Options::instance()->get( 'privacy.enabled' ) ) {
388 $ad_data['ignore_privacy'] = isset( $ad->get_data()['privacy']['ignore-consent'] );
389 }
390
391 /**
392 * Allow add-ons to add more ad data fields.
393 *
394 * @param array $ad_data the fields to be sent back to the browser.
395 * @param Ad $ad the ad being currently edited.
396 */
397 return apply_filters( 'advanced-ads-quick-edit-ad-data', $ad_data, $ad );
398 }
399 }
400