Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
82.86% |
116 / 140 |
|
55.56% |
5 / 9 |
CRAP | |
0.00% |
0 / 1 |
| Metabox | |
82.73% |
115 / 139 |
|
55.56% |
5 / 9 |
20.86 | |
0.00% |
0 / 1 |
| init | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| add_meta_box_callback | |
92.86% |
13 / 14 |
|
0.00% |
0 / 1 |
2.00 | |||
| render_meta_box_content | |
96.30% |
26 / 27 |
|
0.00% |
0 / 1 |
4 | |||
| heading | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
| pending_review_notice | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
6 | |||
| player_embed | |
95.56% |
43 / 45 |
|
0.00% |
0 / 1 |
5 | |||
| errors | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
| help | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
| regenerate_instructions | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | |
| 3 | declare( strict_types = 1 ); |
| 4 | |
| 5 | /** |
| 6 | * BeyondWords Post Metabox. |
| 7 | * |
| 8 | * @package BeyondWords\Editor\Classic |
| 9 | * @author Stuart McAlpine <stu@beyondwords.io> |
| 10 | * @since 3.0.0 |
| 11 | * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods. |
| 12 | */ |
| 13 | |
| 14 | namespace BeyondWords\Editor\Classic; |
| 15 | |
| 16 | /** |
| 17 | * PostMetabox |
| 18 | * |
| 19 | * @since 3.0.0 |
| 20 | * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods. |
| 21 | */ |
| 22 | defined( 'ABSPATH' ) || exit; |
| 23 | |
| 24 | class Metabox { |
| 25 | |
| 26 | /** |
| 27 | * Init. |
| 28 | * |
| 29 | * @since 4.0.0 |
| 30 | * @since 6.0.0 Make static. |
| 31 | * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods. |
| 32 | */ |
| 33 | public static function init() { |
| 34 | add_action( 'add_meta_boxes', [ self::class, 'add_meta_box_callback'] ); |
| 35 | } |
| 36 | |
| 37 | /** |
| 38 | * Adds the meta box container. |
| 39 | * |
| 40 | * @since 6.0.0 Make static. |
| 41 | * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods. |
| 42 | * |
| 43 | * @param string $post_type |
| 44 | */ |
| 45 | public static function add_meta_box_callback( $post_type ) { |
| 46 | $post_types = \BeyondWords\Settings\Utils::get_compatible_post_types(); |
| 47 | |
| 48 | if ( ! in_array( $post_type, $post_types ) ) { |
| 49 | return; |
| 50 | } |
| 51 | |
| 52 | add_meta_box( |
| 53 | 'beyondwords', |
| 54 | __( 'BeyondWords', 'speechkit' ), |
| 55 | [ self::class, 'render_meta_box_content'], |
| 56 | $post_type, |
| 57 | 'side', |
| 58 | 'default', |
| 59 | [ |
| 60 | '__back_compat_meta_box' => true, |
| 61 | ] |
| 62 | ); |
| 63 | } |
| 64 | |
| 65 | /** |
| 66 | * Render Meta Box content. |
| 67 | * |
| 68 | * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods. |
| 69 | * |
| 70 | * @param int|\WP_Post $post The WordPress post ID, or post object. |
| 71 | * |
| 72 | * @since 3.0.0 |
| 73 | * @since 3.7.0 Show "Pending review" notice for posts with status of "pending" |
| 74 | * @since 4.0.0 Content ID is no longer an int |
| 75 | * @since 4.1.0 Add "Player style" and update component display conditions |
| 76 | * @since 6.0.0 Make static and add Magic Embed support. |
| 77 | */ |
| 78 | public static function render_meta_box_content( $post ) { |
| 79 | $post = get_post( $post ); |
| 80 | |
| 81 | if ( ! ( $post instanceof \WP_Post ) ) { |
| 82 | return; |
| 83 | } |
| 84 | |
| 85 | $has_content = \BeyondWords\Post\Meta::has_content( $post->ID ); |
| 86 | |
| 87 | // Single nonce guarding the Content/Format/Player <select> fields. |
| 88 | \BeyondWords\Editor\Components\SettingsFields::nonce(); |
| 89 | |
| 90 | self::heading( __( 'Player', 'speechkit' ) ); |
| 91 | |
| 92 | self::errors( $post ); |
| 93 | |
| 94 | if ( $has_content ) { |
| 95 | if ( get_post_status( $post ) === 'pending' ) { |
| 96 | self::pending_review_notice( $post ); |
| 97 | } else { |
| 98 | self::player_embed( $post ); |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | // The Embed dropdown ("None" = no player) replaces the old Display player checkbox. |
| 103 | \BeyondWords\Editor\Components\SettingsFields::render_player_section( $post ); |
| 104 | ( new \BeyondWords\Editor\Components\GenerateAudio() )::element( $post ); |
| 105 | |
| 106 | echo '<hr />'; |
| 107 | self::heading( __( 'Content', 'speechkit' ) ); |
| 108 | \BeyondWords\Editor\Components\SettingsFields::render_content_section( $post ); |
| 109 | |
| 110 | echo '<hr />'; |
| 111 | self::heading( __( 'Format', 'speechkit' ) ); |
| 112 | \BeyondWords\Editor\Components\SettingsFields::render_format_section( $post ); |
| 113 | |
| 114 | echo '<hr />'; |
| 115 | self::heading( __( 'Voice', 'speechkit' ) ); |
| 116 | ( new \BeyondWords\Editor\Components\SelectVoice() )::element( $post ); |
| 117 | |
| 118 | echo '<hr />'; |
| 119 | self::heading( __( 'Data', 'speechkit' ) ); |
| 120 | \BeyondWords\Editor\Components\ContentId::element( $post ); |
| 121 | |
| 122 | echo '<hr />'; |
| 123 | self::help(); |
| 124 | } |
| 125 | |
| 126 | /** |
| 127 | * Print a settings-section heading. |
| 128 | * |
| 129 | * @since 7.0.0 |
| 130 | * |
| 131 | * @param string $title The section heading. |
| 132 | */ |
| 133 | public static function heading( $title ) { |
| 134 | printf( |
| 135 | '<h4 class="beyondwords-metabox__heading">%s</h4>', |
| 136 | esc_html( $title ) |
| 137 | ); |
| 138 | } |
| 139 | |
| 140 | /** |
| 141 | * The "Pending review" message for posts with "pending" status. |
| 142 | * |
| 143 | * Shown instead of the player, which cannot render audio created with |
| 144 | * { published: false }. |
| 145 | * |
| 146 | * @since 3.7.0 |
| 147 | * @since 6.0.0 Make static. |
| 148 | * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods. |
| 149 | * |
| 150 | * @var int|\WP_Post $post The WordPress post ID, or post object. |
| 151 | */ |
| 152 | public static function pending_review_notice( $post ) { |
| 153 | $post = get_post( $post ); |
| 154 | |
| 155 | if ( ! ( $post instanceof \WP_Post ) ) { |
| 156 | return; |
| 157 | } |
| 158 | |
| 159 | $project_url = sprintf( |
| 160 | '%s/dashboard/project/%d/content', |
| 161 | \BeyondWords\Core\Urls::get_dashboard_url(), |
| 162 | \BeyondWords\Post\Meta::get_project_id( $post->ID ) |
| 163 | ); |
| 164 | |
| 165 | ?> |
| 166 | <div id="beyondwords-pending-review-message"> |
| 167 | <?php |
| 168 | printf( |
| 169 | /* translators: %s is replaced with the link to the BeyondWords dashboard */ |
| 170 | esc_html__( 'Listen to content saved as “Pending” in the %s.', 'speechkit' ), |
| 171 | sprintf( |
| 172 | '<a href="%s" target="_blank" rel="nofollow">%s</a>', |
| 173 | esc_url( $project_url ), |
| 174 | esc_html__( 'BeyondWords dashboard', 'speechkit' ) |
| 175 | ) |
| 176 | ); |
| 177 | ?> |
| 178 | </div> |
| 179 | <?php |
| 180 | } |
| 181 | |
| 182 | /** |
| 183 | * Embed a player for a WordPress post. |
| 184 | * |
| 185 | * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods. |
| 186 | * |
| 187 | * @param int|\WP_Post|null $post (Optional) Post ID, or WP_Post object, or null. |
| 188 | * |
| 189 | * @since 3.x Introduced |
| 190 | * @since 4.0.1 Admin player init is now all in this one function. |
| 191 | * @since 6.0.0 Make static and add Magic Embed support. |
| 192 | */ |
| 193 | public static function player_embed( $post = null ) { |
| 194 | $post = get_post( $post ); |
| 195 | |
| 196 | if ( ! ( $post instanceof \WP_Post ) ) { |
| 197 | return; |
| 198 | } |
| 199 | |
| 200 | $project_id = \BeyondWords\Post\Meta::get_project_id( $post->ID ); |
| 201 | $has_content = \BeyondWords\Post\Meta::has_content( $post->ID ); |
| 202 | |
| 203 | if ( ! $project_id || ! $has_content ) { |
| 204 | return; |
| 205 | } |
| 206 | |
| 207 | $content_id = \BeyondWords\Post\Meta::get_content_id( $post->ID ); |
| 208 | $preview_token = \BeyondWords\Post\Meta::get_preview_token( $post->ID ); |
| 209 | |
| 210 | // phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript |
| 211 | if ( ! empty( $content_id ) ) : |
| 212 | /* |
| 213 | * Still-processing content would 404 (and the CDN would cache it), so |
| 214 | * render a loading state and let classic-metabox.js poll until `processed`. |
| 215 | * The untrusted content ID / preview token travel as esc_attr()'d data-* |
| 216 | * attributes — never interpolated into a JS execution context. |
| 217 | */ |
| 218 | ?> |
| 219 | <div |
| 220 | id="beyondwords-metabox-player" |
| 221 | role="status" |
| 222 | aria-live="polite" |
| 223 | style="margin: 13px 0;" |
| 224 | data-project-id="<?php echo esc_attr( $project_id ); ?>" |
| 225 | data-content-id="<?php echo esc_attr( $content_id ); ?>" |
| 226 | data-preview-token="<?php echo esc_attr( $preview_token ); ?>" |
| 227 | > |
| 228 | <span |
| 229 | class="spinner is-active" |
| 230 | style="float: none; margin: 0 8px 0 0;" |
| 231 | ></span> |
| 232 | <span class="beyondwords-player-loading-text"> |
| 233 | <?php esc_html_e( 'Generating…', 'speechkit' ); ?> |
| 234 | </span> |
| 235 | </div> |
| 236 | <script |
| 237 | defer |
| 238 | src='<?php echo esc_url( \BeyondWords\Core\Urls::get_js_sdk_url() ); ?>' |
| 239 | ></script> |
| 240 | <?php |
| 241 | else : |
| 242 | /* |
| 243 | * Client-side integration is keyed on the source ID — nothing to poll, so |
| 244 | * embed immediately. The untrusted preview token is JSON_HEX_*-encoded so |
| 245 | * no value breaks out of the onload attribute. Mirrors Javascript::render(). |
| 246 | */ |
| 247 | $config = [ |
| 248 | 'projectId' => (int) $project_id, |
| 249 | 'sourceId' => (string) $post->ID, |
| 250 | 'previewToken' => (string) $preview_token, |
| 251 | 'adverts' => [], |
| 252 | 'analyticsConsent' => 'none', |
| 253 | 'introsOutros' => [], |
| 254 | 'playerStyle' => 'small', |
| 255 | 'widgetStyle' => 'none', |
| 256 | ]; |
| 257 | |
| 258 | $onload = sprintf( |
| 259 | 'const player = new BeyondWords.Player({ target: this.parentElement, ...%s });', |
| 260 | wp_json_encode( |
| 261 | $config, |
| 262 | JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP |
| 263 | ) |
| 264 | ); |
| 265 | ?> |
| 266 | <div id="beyondwords-metabox-player" style="margin: 13px 0;"> |
| 267 | <script defer |
| 268 | src='<?php echo esc_url( \BeyondWords\Core\Urls::get_js_sdk_url() ); ?>' |
| 269 | onload='<?php echo esc_attr( $onload ); ?>' |
| 270 | > |
| 271 | </script> |
| 272 | </div> |
| 273 | <?php |
| 274 | endif; |
| 275 | // phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedScript |
| 276 | } |
| 277 | |
| 278 | /** |
| 279 | * Display errors for the post. |
| 280 | * |
| 281 | * @since 6.0.0 Make static. |
| 282 | * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods. |
| 283 | */ |
| 284 | public static function errors( $post ) { |
| 285 | $error = \BeyondWords\Post\Meta::get_error_message( $post->ID ); |
| 286 | |
| 287 | if ( $error ) : |
| 288 | ?> |
| 289 | <div id="beyondwords-metabox-errors"> |
| 290 | <div class="beyondwords-error"> |
| 291 | <p> |
| 292 | <?php echo esc_html( $error ); ?> |
| 293 | </p> |
| 294 | </div> |
| 295 | <?php self::regenerate_instructions(); ?> |
| 296 | </div> |
| 297 | <?php |
| 298 | endif; |
| 299 | } |
| 300 | |
| 301 | /** |
| 302 | * Display help text for the metabox. |
| 303 | * |
| 304 | * @since 6.0.0 Make static. |
| 305 | * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods. |
| 306 | */ |
| 307 | public static function help() { |
| 308 | ?> |
| 309 | <p id="beyondwords-metabox-help"> |
| 310 | <?php |
| 311 | printf( |
| 312 | /* translators: %s is replaced with the link to the support email address */ |
| 313 | esc_html__( 'Need help? Email our support team on %s', 'speechkit' ), |
| 314 | sprintf( '<a href="%s">%s</a>', 'mailto:support@beyondwords.io', 'support@beyondwords.io' ) |
| 315 | ); |
| 316 | ?> |
| 317 | </p> |
| 318 | <?php |
| 319 | } |
| 320 | |
| 321 | /** |
| 322 | * Display instructions for regenerating audio. |
| 323 | * |
| 324 | * @since 6.0.0 Make static. |
| 325 | * @since 7.0.0 Refactored to BeyondWords namespace with snake_case methods. |
| 326 | */ |
| 327 | public static function regenerate_instructions() { |
| 328 | ?> |
| 329 | <!-- Update/regenerate --> |
| 330 | <p> |
| 331 | <?php |
| 332 | esc_html_e( |
| 333 | 'To create audio, resolve the error above then select ‘Update’ with ‘Generate audio’ checked.', // phpcs:ignore Generic.Files.LineLength.TooLong |
| 334 | 'speechkit' |
| 335 | ); |
| 336 | ?> |
| 337 | </p> |
| 338 | <?php |
| 339 | } |
| 340 | } |