Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
1.89% covered (danger)
1.89%
3 / 159
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
Publicize_UI
1.89% covered (danger)
1.89%
3 / 159
0.00% covered (danger)
0.00%
0 / 7
823.29
0.00% covered (danger)
0.00%
0 / 1
 __construct
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 init
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 post_page_metabox_assets
0.00% covered (danger)
0.00%
0 / 56
0.00% covered (danger)
0.00%
0 / 1
42
 connection_label
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 post_page_metabox
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
42
 get_metabox_form_connected
0.00% covered (danger)
0.00%
0 / 55
0.00% covered (danger)
0.00%
0 / 1
110
 get_message_placeholders
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Publicize_UI class.
4 *
5 * @package automattic/jetpack-publicize
6 */
7
8namespace Automattic\Jetpack\Publicize;
9
10use Automattic\Jetpack\Assets;
11use Automattic\Jetpack\Current_Plan;
12use Automattic\Jetpack\Publicize\Publicize_Utils as Utils;
13use Automattic\Jetpack\Status\Host;
14
15/**
16 * Classic editor integration: the Publicize metabox on the post screen, and its assets.
17 */
18class Publicize_UI {
19    /**
20     * Contains an instance of class 'Publicize' which loads Keyring, sets up services, etc.
21     *
22     * @var Publicize Instance of Publicize
23     */
24    public $publicize;
25
26    /**
27     * URL of the admin page where Publicize connections are managed.
28     *
29     * @var string
30     */
31    protected $publicize_settings_url = '';
32
33    /**
34     * Hooks into WordPress to display the various pieces of UI and load our assets
35     */
36    public function __construct() {
37        global $publicize;
38        if ( ! is_object( $publicize ) ) {
39            $publicize = new Publicize();
40        }
41        $this->publicize = $publicize;
42
43        add_action( 'admin_init', array( $this, 'init' ) );
44    }
45
46    /**
47     * Initialize UI-related functionality.
48     */
49    public function init() {
50        // Show only to users with the capability required to manage their Publicize connections.
51        if ( ! Utils::is_publicize_active() || ! $this->publicize->current_user_can_access_publicize_data() ) {
52            return;
53        }
54
55        $this->publicize_settings_url = $this->publicize->publicize_connections_url();
56
57        // Assets (css, js).
58        add_action( 'admin_head-post.php', array( $this, 'post_page_metabox_assets' ) );
59        add_action( 'admin_head-post-new.php', array( $this, 'post_page_metabox_assets' ) );
60
61        add_action( 'post_submitbox_misc_actions', array( $this, 'post_page_metabox' ) );
62    }
63
64    /**
65     * CSS for styling the publicize message box and counter that displays on the post page.
66     * There is also some JavaScript for length counting and some basic display effects.
67     */
68    public function post_page_metabox_assets() {
69        // We don't need those assets for the block editor pages.
70        $current_screen = get_current_screen();
71        if ( $current_screen && $current_screen->is_block_editor ) {
72            return;
73        }
74
75        $is_atomic_site = ( new Host() )->is_woa_site();
76        $is_simple_site = ( new Host() )->is_wpcom_simple();
77        $site_type      = $is_atomic_site ? 'atomic' : ( $is_simple_site ? 'simple' : 'jetpack' );
78
79        Assets::register_script(
80            'jetpack-social-classic-editor-options',
81            '../build/classic-editor.js',
82            __FILE__,
83            array(
84                'in_footer'  => true,
85                'enqueue'    => true,
86                'textdomain' => 'jetpack-publicize-pkg',
87            )
88        );
89
90        wp_add_inline_script(
91            'jetpack-social-classic-editor-options',
92            'var jetpackSocialClassicEditorOptions = ' . wp_json_encode(
93                array(
94                    'connectionsUrl'              => esc_url( $this->publicize_settings_url ),
95                    'isEnhancedPublishingEnabled' => $this->publicize->has_enhanced_publishing_feature(),
96                    'resharePath'                 => '/wpcom/v2/publicize/share-post/{postId}',
97                    'refreshConnections'          => '/wpcom/v2/publicize/connections?test_connections=1',
98                    'isReshareSupported'          => Current_Plan::supports( 'republicize' ),
99                    'siteType'                    => $site_type,
100                ),
101                JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
102            ),
103            'before'
104        );
105
106        $default_prefix = $this->publicize->default_prefix;
107        $default_prefix = preg_replace( '/%([0-9])\$s/', '" + %\\1$s + "', wp_json_encode( (string) $default_prefix, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) );
108
109        $default_message = $this->publicize->default_message;
110        $default_message = preg_replace( '/%([0-9])\$s/', '" + %\\1$s + "', wp_json_encode( (string) $default_message, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) );
111
112        $default_suffix = $this->publicize->default_suffix;
113        $default_suffix = preg_replace( '/%([0-9])\$s/', '" + %\\1$s + "', wp_json_encode( (string) $default_suffix, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) );
114
115        $max_length  = defined( 'JETPACK_PUBLICIZE_TWITTER_LENGTH' ) ? JETPACK_PUBLICIZE_TWITTER_LENGTH : 280;
116        $max_length -= 24; // t.co link, space.
117
118        ?>
119
120<script type="text/javascript">
121jQuery( function($) {
122    var wpasTitleCounter    = $( '#wpas-title-counter' ),
123        wpasTwitterCheckbox = $( '.wpas-submit-twitter' ).length,
124        postTitle = $( '#title' ),
125        wpasTitle = $( '#wpas-title' ).keyup( function() {
126            var postTitleVal,
127                length = wpasTitle.val().length;
128
129            if ( ! length ) {
130                length = wpasTitle.attr( 'placeholder' ).length;
131            }
132
133            wpasTitleCounter.text( length ).trigger( 'change' );
134        } ),
135        authClick = false;
136
137    wpasTitleCounter.on( 'change', function( e ) {
138        if ( wpasTwitterCheckbox && parseInt( $( e.currentTarget ).text(), 10 ) > <?php echo (int) $max_length; ?> ) {
139            wpasTitleCounter.addClass( 'wpas-twitter-length-limit' );
140        } else {
141            wpasTitleCounter.removeClass( 'wpas-twitter-length-limit' );
142        }
143    } );
144
145    // Keep the postTitle and the placeholder in sync
146    postTitle.on( 'keyup', function( e ) {
147        var url = $( '#sample-permalink' ).text();
148        <?php // phpcs:ignore ?>
149        var defaultMessage = $.trim( <?php printf( $default_prefix, 'url' ); ?> + <?php printf( $default_message, 'e.currentTarget.value', 'url' ); ?> + <?php printf( $default_suffix, 'url' ); ?> )
150            .replace( /<[^>]+>/g,'');
151
152        wpasTitle.attr( 'placeholder', defaultMessage );
153        wpasTitle.trigger( 'keyup' );
154    } );
155
156    // set the initial placeholder
157    postTitle.trigger( 'keyup' );
158
159    // If a custom message has been provided, open the UI so the author remembers
160    if ( wpasTitle.val() && ! wpasTitle.prop( 'disabled' ) && wpasTitle.attr( 'placeholder' ) !== wpasTitle.val() ) {
161        $( '#publicize-form' ).show();
162        $( '#publicize-defaults' ).hide();
163        $( '#publicize-form-edit' ).hide();
164    }
165
166    $('#publicize-disconnected-form-show').click( function() {
167        $('#publicize-form').slideDown( 'fast' );
168        $(this).hide();
169    } );
170
171    $('#publicize-disconnected-form-hide').click( function() {
172        $('#publicize-form').slideUp( 'fast' );
173        $('#publicize-disconnected-form-show').show();
174    } );
175
176    $('#publicize-form-edit').click( function() {
177        $('#publicize-form').slideDown( 'fast', function() {
178            var selBeg = 0, selEnd = 0;
179            wpasTitle.focus();
180
181            if ( ! wpasTitle.text() ) {
182                wpasTitle.text( wpasTitle.attr( 'placeholder' ) );
183
184                selBeg = wpasTitle.text().indexOf( postTitle.val() );
185                if ( selBeg < 0 ) {
186                    selBeg = 0;
187                } else {
188                    selEnd = selBeg + postTitle.val().length;
189                }
190
191                var domObj = wpasTitle.get(0);
192                if ( domObj.setSelectionRange ) {
193                    domObj.setSelectionRange( selBeg, selEnd );
194                } else if ( domObj.createTextRange ) {
195                    var r = domObj.createTextRange();
196                    r.moveStart( 'character', selBeg );
197                    r.moveEnd( 'character', selEnd );
198                    r.select();
199                }
200            }
201        } );
202
203        $('#publicize-defaults').hide();
204        $(this).hide();
205        return false;
206    } );
207
208
209    $('#publicize-form-hide').click( function() {
210        var newList = $.map( $('#publicize-form').slideUp( 'fast' ).find( ':checked' ), function( el ) {
211            return $.trim( $(el).parent( 'label' ).text() );
212        } );
213        $('#publicize-defaults').html( '<strong>' + newList.join( '</strong>, <strong>' ) + '</strong>' ).show();
214        $('#publicize-form-edit').show();
215        return false;
216    } );
217
218    $('.authorize-link').click( function() {
219        if ( authClick ) {
220            return false;
221        }
222        authClick = true;
223        $(this).after( '<img src="images/loading.gif" class="alignleft" style="margin: 0 .5em" />' );
224        $.ajaxSetup( { async: false } );
225
226        if ( window.wp && window.wp.autosave ) {
227            window.wp.autosave.server.triggerSave();
228        } else {
229            autosave();
230        }
231
232        return true;
233    } );
234
235    $( '.pub-service' ).click( function() {
236        var service = $(this).data( 'service' ),
237            fakebox = '<input id="wpas-submit-' + service + '" type="hidden" value="1" name="wpas[submit][' + service + ']" />';
238        $( '#add-publicize-check' ).append( fakebox );
239    } );
240} );
241</script>
242
243<style type="text/css">
244#publicize {
245    line-height: 1.5;
246}
247#publicize ul {
248    margin: 4px 0 4px 6px;
249}
250#publicize li {
251    margin: 0;
252}
253#publicize textarea {
254    margin: 4px 0 0;
255    width: 100%
256}
257#publicize ul.not-connected {
258    list-style: square;
259    padding-left: 1em;
260}
261.wpas-disabled {
262    color: #999;
263}
264.publicize__notice-warning {
265    display: block;
266    padding: 7px 10px;
267    margin: 5px 0;
268    border-left-width: 4px;
269    border-left-style: solid;
270    font-size: 12px;
271    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
272}
273.publicize__notice-media-warning {
274    border-right: 1px solid #c3c4c7;
275    border-bottom: 1px solid #c3c4c7;
276    border-top: 1px solid #c3c4c7;
277}
278.publicize-external-link {
279    display: block;
280    text-decoration: none;
281    margin-top: 8px;
282}
283.publicize-external-link__text {
284    text-decoration: underline;
285}
286#publicize-title::before {
287    content: "\f237";
288    font: normal 20px/1 dashicons;
289    speak: none;
290    margin-left: -1px;
291    padding-right: 3px;
292    vertical-align: top;
293    -webkit-font-smoothing: antialiased;
294    color: #8c8f94;
295}
296.post-new-php .authorize-link, .post-php .authorize-link {
297    line-height: 1.5em;
298}
299.post-new-php .authorize-message, .post-php .authorize-message {
300    margin-bottom: 0;
301}
302#poststuff #publicize .updated p {
303    margin: .5em 0;
304}
305.wpas-twitter-length-limit {
306    color: red;
307}
308.publicize__notice-warning .dashicons {
309    font-size: 16px;
310    text-decoration: none;
311}
312.publicize-placeholders-help {
313    margin: 0.5rem 0 1rem;
314}
315</style>
316        <?php
317    }
318
319    /**
320     * Get the connection label.
321     *
322     * @param string $service_label Service's human-readable Label ("Facebook", "Twitter", ...).
323     * @param string $display_name Connection's human-readable Username ("@jetpack", ...).
324     * @return string
325     */
326    private function connection_label( $service_label, $display_name ) {
327        return sprintf(
328            /* translators: %1$s: Service Name (Facebook, Twitter, ...), %2$s: Username on Service (@jetpack, ...) */
329            __( '%1$s: %2$s', 'jetpack-publicize-pkg' ),
330            $service_label,
331            $display_name
332        );
333    }
334
335    /**
336     * Controls the metabox that is displayed on the post page
337     * Allows the user to customize the message that will be sent out to the social network, as well as pick which
338     * networks to publish to. Also displays the character counter and some other information.
339     */
340    public function post_page_metabox() {
341        global $post;
342
343        if ( ! $this->publicize->post_type_is_publicizeable( $post->post_type ) ) {
344            return;
345        }
346
347        $connections_data = $this->publicize->get_filtered_connection_data();
348
349        if ( ! is_array( $connections_data ) ) {
350            $connections_data = array();
351        }
352        ?>
353        <div id="publicize" class="misc-pub-section misc-pub-section-last">
354            <span id="publicize-title">
355            <?php
356            esc_html_e( 'Jetpack Social:', 'jetpack-publicize-pkg' );
357
358            if ( ! empty( $connections_data ) ) :
359                $publicize_form = $this->get_metabox_form_connected( $connections_data );
360
361                $labels = array();
362
363                foreach ( $connections_data as $connection_data ) {
364                    if ( ! $connection_data['enabled'] ) {
365                        continue;
366                    }
367
368                    $labels[] = sprintf(
369                        '<strong>%s</strong>',
370                        esc_html( $this->connection_label( $connection_data['service_label'], $connection_data['display_name'] ) )
371                    );
372                }
373
374                ?>
375                    <?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- labels are already escaped above ?>
376                    <span id="publicize-defaults"><?php echo implode( ', ', $labels ); ?></span>
377                    <a href="#" id="publicize-form-edit"><?php esc_html_e( 'Edit', 'jetpack-publicize-pkg' ); ?></a>&nbsp;<a href="<?php echo esc_url( $this->publicize->publicize_connections_url() ); ?>" rel="noopener noreferrer" target="_blank"><?php esc_html_e( 'Settings', 'jetpack-publicize-pkg' ); ?></a><br />
378                    <?php
379            else :
380                $publicize_form = '';
381                ?>
382                <strong><?php esc_html_e( 'Not Connected', 'jetpack-publicize-pkg' ); ?></strong>
383                <a href="<?php echo esc_url( $this->publicize->publicize_connections_url() ); ?>" rel="noopener noreferrer" target="_blank"><?php esc_html_e( 'Settings', 'jetpack-publicize-pkg' ); ?></a><br />
384                <?php
385
386            endif;
387            ?>
388            </span>
389            <?php
390            /**
391             * Fires right before rendering the Publicize form in the Classic
392             * Editor.
393             *
394             * @since 0.14.0
395             */
396            do_action( 'publicize_classic_editor_form_before' );
397
398            /**
399             * Filter the Publicize details form.
400             *
401             * @since 0.1.0
402             * @since-jetpack 2.0.0
403             *
404             * @param string $publicize_form Publicize Details form appearing above Publish button in the editor.
405             */
406            echo apply_filters( 'publicize_form', $publicize_form ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Parts of the form are escaped individually in the code above.
407
408            /**
409             * Fires right after rendering the Publicize form in the Classic
410             * Editor.
411             *
412             * @since 0.14.0
413             */
414            do_action( 'publicize_classic_editor_form_after' );
415            ?>
416        </div>
417        <?php
418    }
419
420    /**
421     * Generates HTML content for connections form.
422     *
423     * @since 0.1.0
424     * @since-jetpack 6.7.0
425     *
426     * @global WP_Post $post The current post instance being published.
427     *
428     * @param array $connections_data Array of connections.
429     * @return array {
430     *     Array of content for generating connection form.
431     *
432     *     @type string HTML content of form
433     *     @type array {
434     *          Array of connection labels for active connections only.
435     *
436     *          @type string Connection label string.
437     *     }
438     * }
439     */
440    private function get_metabox_form_connected( $connections_data ) {
441        global $post;
442
443        ob_start();
444
445        ?>
446        <div id="publicize-form" class="hide-if-js">
447            <ul>
448        <?php
449
450        foreach ( $connections_data as $connection_data ) {
451            ?>
452
453            <li>
454                <label
455                    for="wpas-submit-<?php echo esc_attr( $connection_data['connection_id'] ); ?>"
456                >
457                    <input
458                        type="checkbox"
459                        name="wpas[submit][<?php echo esc_attr( $connection_data['connection_id'] ); ?>]"
460                        id="wpas-submit-<?php echo esc_attr( $connection_data['connection_id'] ); ?>"
461                        class="wpas-submit-<?php echo esc_attr( $connection_data['service_name'] ); ?>"
462                        value="1"
463                        data-id="<?php echo esc_attr( $connection_data['connection_id'] ); ?>"
464                    <?php
465                        checked( true, $connection_data['enabled'] );
466                    ?>
467                    />
468                    <?php echo esc_html( $this->connection_label( $connection_data['service_label'], $connection_data['display_name'] ) ); ?>
469
470                </label>
471            </li>
472            <?php
473        }
474
475        $title = get_post_meta( $post->ID, $this->publicize->POST_MESS, true );
476        if ( ! $title ) {
477            $title = '';
478        }
479
480        $is_social_note = 'jetpack-social-note' === get_post_type( $post->ID );
481
482        $is_post_published = 'publish' === get_post_status( $post->ID );
483
484        $templates_enabled = $this->publicize->has_paid_features();
485
486        $placeholders = $this->get_message_placeholders();
487
488        ?>
489
490            </ul>
491
492            <?php if ( ! $is_social_note ) : ?>
493                <label for="wpas-title"><?php esc_html_e( 'Custom Message:', 'jetpack-publicize-pkg' ); ?></label>
494                <?php if ( ! $templates_enabled ) : ?>
495                    <span id="wpas-title-counter" class="alignright hide-if-no-js">0</span>
496                <?php endif; ?>
497                <textarea name="wpas_title" id="wpas-title"><?php echo esc_textarea( $title ); ?></textarea>
498                <?php if ( $templates_enabled && ! empty( $placeholders ) ) : ?>
499                    <details class="publicize-placeholders-help">
500                        <summary><?php esc_html_e( 'Available placeholders', 'jetpack-publicize-pkg' ); ?></summary>
501                        <p>
502                            <?php esc_html_e( 'Use placeholders to automatically insert post details.', 'jetpack-publicize-pkg' ); ?>
503                        </p>
504                        <ul>
505                            <?php foreach ( $placeholders as $placeholder ) : ?>
506                                <li>
507                                    <code><?php echo esc_html( $placeholder['id'] ); ?></code>
508                                    &mdash; <?php echo esc_html( $placeholder['label'] ); ?>
509                                </li>
510                            <?php endforeach; ?>
511                        </ul>
512                    </details>
513                <?php endif; ?>
514                <a href="#" class="hide-if-no-js button" id="publicize-form-hide"><?php esc_html_e( 'OK', 'jetpack-publicize-pkg' ); ?></a>
515                <input type="hidden" name="wpas[0]" value="1" />
516            <?php endif; ?>
517            <?php if ( $is_post_published && Current_Plan::supports( 'republicize' ) ) : ?>
518                <button type="button" class="hide-if-no-js button" id="publicize-share-now">
519                    <?php esc_html_e( 'Share now', 'jetpack-publicize-pkg' ); ?>
520                </button>
521                <span id="publicize-share-now-notice" class="hidden"></span>
522            <?php endif; ?>
523        </div>
524
525        <div id="pub-connection-needs-media"></div>
526        <div id="pub-connection-tests"></div>
527        <?php
528
529        return ob_get_clean();
530    }
531
532    /**
533     * Get the catalogue of placeholders supported in custom Publicize messages.
534     *
535     * Sourced from WPCOM via `Message_Templates_Placeholders` so the metabox
536     * hint can never drift from the resolver in `Template_Parser`.
537     *
538     * @return array<int, array{id: string, label: string}> Ordered list of placeholders.
539     */
540    private function get_message_placeholders() {
541        return Message_Templates_Placeholders::get_all();
542    }
543}