PluginProbe
BeyondWords – AI audio for publishers / 7.0.0
BeyondWords – AI audio for publishers v7.0.0
7.1.0 trunk 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.1.0 4.1.1 4.1.2 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.3.0 4.4.0 4.5.0 4.5.1 4.6.0 4.6.1 4.6.2 4.7.0 All 43 releases
speechkit / src / posts-list / class-notices.php

class-notices.php in BeyondWords – AI audio for publishers 7.0.0, at src/posts-list/class-notices.php

239 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin notices for the BeyondWords bulk-edit actions.
4 *
5 * @package BeyondWords\PostsList
6 * @since 3.0.0
7 * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods.
8 */
9
10 declare( strict_types = 1 );
11
12 namespace BeyondWords\PostsList;
13
14 defined( 'ABSPATH' ) || exit;
15
16 /**
17 * One notice per bulk-action result, all nonce-gated against direct URL fiddling.
18 *
19 * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods.
20 */
21 class Notices {
22
23 /**
24 * Register WordPress hooks.
25 */
26 public static function init(): void {
27 add_action( 'admin_notices', [ self::class, 'generated_notice' ] );
28 add_action( 'admin_notices', [ self::class, 'skipped_notice' ] );
29 add_action( 'admin_notices', [ self::class, 'deferred_notice' ] );
30 add_action( 'admin_notices', [ self::class, 'deleted_notice' ] );
31 add_action( 'admin_notices', [ self::class, 'failed_notice' ] );
32 add_action( 'admin_notices', [ self::class, 'error_notice' ] );
33 }
34
35 /**
36 * "Audio was requested for N posts." notice after a Generate Audio bulk action.
37 */
38 public static function generated_notice(): void {
39 $count = self::get_query_count( 'beyondwords_bulk_generated' );
40
41 if ( null === $count ) {
42 return;
43 }
44
45 $message = sprintf(
46 /* translators: %d is replaced with the number of posts processed */
47 _n(
48 'Audio was requested for %d post.',
49 'Audio was requested for %d posts.',
50 $count,
51 'speechkit'
52 ),
53 $count
54 );
55 ?>
56 <div id="beyondwords-bulk-edit-notice-generated" class="notice notice-info is-dismissible">
57 <p><?php echo esc_html( $message ); ?></p>
58 </div>
59 <?php
60 }
61
62 /**
63 * "N posts were skipped" notice after a Generate Audio bulk action.
64 *
65 * Skipped posts had nothing to generate — an ineligible post status, an
66 * existing recording with autoregeneration off, or a concurrent create.
67 */
68 public static function skipped_notice(): void {
69 $count = self::get_query_count( 'beyondwords_bulk_skipped' );
70
71 if ( null === $count ) {
72 return;
73 }
74
75 $message = sprintf(
76 /* translators: %d is replaced with the number of posts that were skipped */
77 _n(
78 '%d post was skipped because no audio change was needed.',
79 '%d posts were skipped because no audio change was needed.',
80 $count,
81 'speechkit'
82 ),
83 $count
84 );
85 ?>
86 <div id="beyondwords-bulk-edit-notice-skipped" class="notice notice-info is-dismissible">
87 <p><?php echo esc_html( $message ); ?></p>
88 </div>
89 <?php
90 }
91
92 /**
93 * "N posts still need audio" notice after a bulk action hit the off-VIP sync cap.
94 *
95 * Deferred posts already have their generate flag set, so re-running the
96 * action completes them.
97 */
98 public static function deferred_notice(): void {
99 $count = self::get_query_count( 'beyondwords_bulk_deferred' );
100
101 if ( null === $count ) {
102 return;
103 }
104
105 $message = sprintf(
106 /* translators: %d is replaced with the number of posts not yet processed */
107 _n(
108 '%d post still needs audio — run Generate audio again to continue.',
109 '%d posts still need audio — run Generate audio again to continue.',
110 $count,
111 'speechkit'
112 ),
113 $count
114 );
115 ?>
116 <div id="beyondwords-bulk-edit-notice-deferred" class="notice notice-warning is-dismissible">
117 <p><?php echo esc_html( $message ); ?></p>
118 </div>
119 <?php
120 }
121
122 /**
123 * "Audio was deleted for N posts." notice after a Delete Audio bulk action.
124 */
125 public static function deleted_notice(): void {
126 $count = self::get_query_count( 'beyondwords_bulk_deleted' );
127
128 if ( null === $count ) {
129 return;
130 }
131
132 $message = sprintf(
133 /* translators: %d is replaced with the number of posts processed */
134 _n(
135 'Audio was deleted for %d post.',
136 'Audio was deleted for %d posts.',
137 $count,
138 'speechkit'
139 ),
140 $count
141 );
142 ?>
143 <div id="beyondwords-bulk-edit-notice-deleted" class="notice notice-info is-dismissible">
144 <p><?php echo esc_html( $message ); ?></p>
145 </div>
146 <?php
147 }
148
149 /**
150 * "N posts failed." notice after a bulk action where some items errored.
151 */
152 public static function failed_notice(): void {
153 $count = self::get_query_count( 'beyondwords_bulk_failed' );
154
155 if ( null === $count ) {
156 return;
157 }
158
159 $message = sprintf(
160 /* translators: %d is replaced with the number of posts that failed */
161 _n(
162 '%d post failed, check for errors in the BeyondWords column below.',
163 '%d posts failed, check for errors in the BeyondWords column below.',
164 $count,
165 'speechkit'
166 ),
167 $count
168 );
169 ?>
170 <div id="beyondwords-bulk-edit-notice-failed" class="notice notice-error is-dismissible">
171 <p><?php echo esc_html( $message ); ?></p>
172 </div>
173 <?php
174 }
175
176 /**
177 * Top-level error notice after a bulk action that threw.
178 */
179 public static function error_notice(): void {
180 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
181 if ( ! self::verify_result_nonce() || ! isset( $_GET['beyondwords_bulk_error'] ) ) {
182 return;
183 }
184
185 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
186 $message = sanitize_text_field( wp_unslash( $_GET['beyondwords_bulk_error'] ) );
187
188 if ( '' === $message ) {
189 return;
190 }
191 ?>
192 <div id="beyondwords-bulk-edit-notice-error" class="notice notice-error is-dismissible">
193 <p><?php echo esc_html( $message ); ?></p>
194 </div>
195 <?php
196 }
197
198 /**
199 * Verify the result nonce in bulk-action redirects, dying via `wp_nonce_ays()` on tamper.
200 *
201 * Returns false (without exiting) when the nonce param is absent so callers
202 * can short-circuit normal page loads cheaply.
203 */
204 private static function verify_result_nonce(): bool {
205 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
206 if ( ! isset( $_GET['beyondwords_bulk_edit_result_nonce'] ) ) {
207 return false;
208 }
209
210 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
211 if ( ! wp_verify_nonce( sanitize_key( wp_unslash( $_GET['beyondwords_bulk_edit_result_nonce'] ) ), 'beyondwords_bulk_edit_result' ) ) {
212 wp_nonce_ays( '' );
213 }
214
215 return true;
216 }
217
218 /**
219 * Read a positive integer count from `$_GET[$key]` after verifying the result nonce.
220 *
221 * @return int|null `null` when nonce missing, count param missing, or count not positive.
222 */
223 private static function get_query_count( string $key ): ?int {
224 if ( ! self::verify_result_nonce() ) {
225 return null;
226 }
227
228 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
229 if ( ! isset( $_GET[ $key ] ) ) {
230 return null;
231 }
232
233 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
234 $count = (int) sanitize_text_field( wp_unslash( $_GET[ $key ] ) );
235
236 return $count > 0 ? $count : null;
237 }
238 }
239