Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 102
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Note
0.00% covered (danger)
0.00%
0 / 102
0.00% covered (danger)
0.00%
0 / 9
506
0.00% covered (danger)
0.00%
0 / 1
 enabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 init
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
20
 admin_init_actions
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 add_filters_and_actions_for_screen
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 set_empty_title
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 register_cpt
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 1
2
 restrict_blocks_for_social_note
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 maybe_flush_rewrite_rules
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 override_empty_title
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Register the Social note custom post type.
4 *
5 * @package automattic/jetpack-social-plugin
6 */
7
8namespace Automattic\Jetpack\Social;
9
10use Automattic\Jetpack\Constants;
11
12/**
13 * Register the Jetpack Social Note custom post type.
14 */
15class Note {
16    const JETPACK_SOCIAL_NOTE_CPT     = 'jetpack-social-note';
17    const JETPACK_SOCIAL_NOTES_CONFIG = 'jetpack_social_notes_config';
18    const FLUSH_REWRITE_RULES_FLUSHED = 'jetpack_social_rewrite_rules_flushed';
19
20    /**
21     * Check if the feature is enabled.
22     */
23    public function enabled() {
24        return (bool) get_option( self::JETPACK_SOCIAL_NOTE_CPT );
25    }
26
27    /**
28     * Initialize the Jetpack Social Note custom post type.
29     */
30    public function init() {
31        if ( ! self::enabled() ) {
32            return;
33        }
34        add_filter( 'allowed_block_types_all', array( $this, 'restrict_blocks_for_social_note' ), 10, 2 );
35
36        /*
37         * The ActivityPub plugin has a block to set a Fediverse post that a new post is in reply to. This is perfect for Social Notes.
38         */
39        if ( Constants::get_constant( 'ACTIVITYPUB_PLUGIN_VERSION' ) ) {
40            add_filter(
41                'jetpack_social_allowed_blocks',
42                function ( $allowed_blocks ) {
43                    $allowed_blocks[] = 'activitypub/reply';
44                    return $allowed_blocks;
45                }
46            );
47        }
48
49        self::register_cpt();
50        add_action( 'wp_insert_post_data', array( $this, 'set_empty_title' ), 10, 2 );
51        add_action( 'admin_init', array( $this, 'admin_init_actions' ) );
52
53        if (
54            /**
55             * Filters whether to override the empty title for Social Notes on the frontend.
56             *
57             * @since 7.1.0
58             *
59             * @param bool $override_empty_title Whether to override the empty title for Social Notes on the frontend.
60             */
61            apply_filters( 'jetpack_social_notes_override_empty_title', false )
62        ) {
63            add_filter( 'the_title', array( $this, 'override_empty_title' ), 10, 2 );
64        }
65    }
66
67    /**
68     * Things to do on admin_init.
69     */
70    public function admin_init_actions() {
71        \Automattic\Jetpack\Post_List\Post_List::setup();
72        add_action( 'current_screen', array( $this, 'add_filters_and_actions_for_screen' ), 5 );
73    }
74
75    /**
76     * If the current_screen has 'edit' as the base, add filter to change the post list tables.
77     *
78     * @param object $current_screen The current screen.
79     */
80    public function add_filters_and_actions_for_screen( $current_screen ) {
81        if ( 'edit' !== $current_screen->base ) {
82            return;
83        }
84
85        add_filter( 'the_title', array( $this, 'override_empty_title' ), 10, 2 );
86    }
87
88    /**
89     * Set the title to empty string.
90     *
91     * @param array $data The Post Data.
92     * @param array $post The Post.
93     */
94    public function set_empty_title( $data, $post ) {
95        if ( self::JETPACK_SOCIAL_NOTE_CPT === $post['post_type'] && 'auto-draft' === $post['post_status'] ) {
96            $data['post_title'] = '';
97        }
98        return $data;
99    }
100
101    /**
102     * Register the Jetpack Social Note custom post type.
103     */
104    public function register_cpt() {
105        $args = array(
106            'public'       => true,
107            'labels'       => array(
108                'name'                  => esc_html__( 'Social Notes', 'jetpack-social' ),
109                'singular_name'         => esc_html__( 'Social Note', 'jetpack-social' ),
110                'menu_name'             => esc_html__( 'Social Notes', 'jetpack-social' ),
111                'name_admin_bar'        => esc_html__( 'Social Note', 'jetpack-social' ),
112                'add_new'               => esc_html__( 'Add New', 'jetpack-social' ),
113                'add_new_item'          => esc_html__( 'Add New Note', 'jetpack-social' ),
114                'new_item'              => esc_html__( 'New Note', 'jetpack-social' ),
115                'edit_item'             => esc_html__( 'Edit Note', 'jetpack-social' ),
116                'view_item'             => esc_html__( 'View Note', 'jetpack-social' ),
117                'all_items'             => esc_html__( 'All Notes', 'jetpack-social' ),
118                'search_items'          => esc_html__( 'Search Notes', 'jetpack-social' ),
119                'parent_item_colon'     => esc_html__( 'Parent Notes:', 'jetpack-social' ),
120                'not_found'             => esc_html__( 'No Notes found.', 'jetpack-social' ),
121                'not_found_in_trash'    => esc_html__( 'No Notes found in Trash.', 'jetpack-social' ),
122                'archives'              => esc_html__( 'Notes archives', 'jetpack-social' ),
123                'insert_into_item'      => esc_html__( 'Insert into Note', 'jetpack-social' ),
124                'uploaded_to_this_item' => esc_html__( 'Uploaded to this Note', 'jetpack-social' ),
125                'filter_items_list'     => esc_html__( 'Filter Notes list', 'jetpack-social' ),
126                'items_list_navigation' => esc_html__( 'Notes list navigation', 'jetpack-social' ),
127                'items_list'            => esc_html__( 'Notes list', 'jetpack-social' ),
128            ),
129            'show_in_rest' => true,
130            'has_archive'  => true,
131            'supports'     => array( 'editor', 'comments', 'thumbnail', 'publicize', 'enhanced_post_list', 'activitypub' ),
132            'menu_icon'    => 'dashicons-welcome-write-blog',
133            'rewrite'      => array( 'slug' => 'sn' ),
134            'template'     => array(
135                array(
136                    'core/paragraph',
137                    array(
138                        'placeholder' => __( "What's on your mind?", 'jetpack-social' ),
139                    ),
140                ),
141                // We should add this back when the double featured image issue is fixed.
142                // array(
143                // 'core/post-featured-image',
144                // ),
145            ),
146        );
147        register_post_type( self::JETPACK_SOCIAL_NOTE_CPT, $args );
148        self::maybe_flush_rewrite_rules();
149    }
150
151    /**
152     * Restrict the blocks for the Social Note CPT.
153     *
154     * @param array                    $allowed_blocks The allowed blocks.
155     * @param \WP_Block_Editor_Context $block_editor_context The current block editor context.
156     * @return array The allowed blocks.
157     */
158    public function restrict_blocks_for_social_note( $allowed_blocks, $block_editor_context ) {
159        if (
160            ! isset( $block_editor_context->post )
161            || ! $block_editor_context->post instanceof \WP_Post
162            || 'jetpack-social-note' !== $block_editor_context->post->post_type
163        ) {
164            return $allowed_blocks;
165        }
166
167        // Only allow the paragraph block and the featured image block.
168        $allowed_blocks = array(
169            'core/paragraph',
170            'core/post-featured-image',
171        );
172
173        /**
174         * Filters the blocks available to the Social Notes CPT.
175         *
176         * Default is ['core/paragraph', 'core/post-featured-image']
177         *
178         * @since 5.5.0
179         *
180         * @param array $allowed_blocks A linear array of blocks allowed by the CPT.
181         */
182        return apply_filters( 'jetpack_social_allowed_blocks', $allowed_blocks );
183    }
184
185    /**
186     * Flush rewrite rules so the post permalink works correctly for the Social Note CPT. Flushing is an expensive operation, so do only when necessary.
187     *
188     * @param boolean $force Force flush the rewrite rules.
189     */
190    public function maybe_flush_rewrite_rules( $force = false ) {
191        if ( empty( get_option( self::FLUSH_REWRITE_RULES_FLUSHED ) ) || $force ) {
192            flush_rewrite_rules( false );
193            update_option( self::FLUSH_REWRITE_RULES_FLUSHED, true );
194        }
195    }
196
197    /**
198     * Use the_title hook so we show the social note's exceprt in the post list view.
199     *
200     * @param array $title The title of the post, which we have set to be an empty string for Social Notes.
201     * @param array $post_id The Post ID.
202     */
203    public function override_empty_title( $title, $post_id ) {
204        $post = get_post( $post_id );
205
206        if (
207            $post instanceof \WP_Post &&
208            self::JETPACK_SOCIAL_NOTE_CPT === $post->post_type
209        ) {
210            $publishing_date = new \DateTimeImmutable(
211                $post->post_date,
212                wp_timezone()
213            );
214
215            $datetime_format = sprintf(
216                /* Translators: %1$s is a formatted date, e.g. June 18, 2025. %2$s is a formatted time, e.g. 8:24 am. All other words/letters need to be escaped. */
217                __( '%1$s \a\t %2$s', 'jetpack-social' ),
218                get_option( 'date_format' ),
219                get_option( 'time_format' )
220            );
221
222            $title = sprintf(
223                /* Translators: placeholder is a fully-formatted date. */
224                __( 'Social note, %1$s', 'jetpack-social' ),
225                wp_date(
226                    $datetime_format,
227                    $publishing_date->getTimestamp()
228                )
229            );
230
231            /**
232             * Filters the default title for a Social Note.
233             *
234             * @since 7.1.0
235             *
236             * @param string $title The default title.
237             * @param \WP_Post $post The post.
238             */
239            $title = apply_filters( 'jetpack_social_notes_default_title', $title, $post );
240        }
241
242        return $title;
243    }
244}