Welcome to WordPress. This is your first post. Edit or delete it, then start writing!
82 |├── codeception.dist.yml ├── composer.json ├── inc ├── class-clone-blocks.php ├── class-helpers.php ├── class-options.php ├── class-rest-api.php └── class-srb-rest-block-controller.php ├── readme.md ├── readme.txt ├── shared-reusable-blocks.php └── tests ├── _data ├── .gitkeep └── dump.sql ├── _output ├── .gitkeep ├── TestCept.fail.html └── debug │ └── login_page.png ├── _support ├── AcceptanceTester.php ├── FunctionalTester.php ├── Helper │ ├── Acceptance.php │ ├── Functional.php │ ├── Unit.php │ └── Wpunit.php ├── UnitTester.php ├── WpunitTester.php └── _generated │ ├── AcceptanceTesterActions.php │ ├── FunctionalTesterActions.php │ ├── UnitTesterActions.php │ └── WpunitTesterActions.php ├── acceptance.suite.yml ├── acceptance └── Test.feature ├── functional.suite.yml ├── functional ├── AnotherCept.php ├── TestCept.php └── _bootstrap.php ├── readme.txt ├── unit.suite.yml └── wpunit.suite.yml /codeception.dist.yml: -------------------------------------------------------------------------------- 1 | paths: 2 | tests: tests 3 | output: tests/_output 4 | data: tests/_data 5 | support: tests/_support 6 | envs: tests/_envs 7 | actor_suffix: Tester 8 | extensions: 9 | enabled: 10 | - Codeception\Extension\RunFailed 11 | commands: 12 | - Codeception\Command\GenerateWPUnit 13 | - Codeception\Command\GenerateWPRestApi 14 | - Codeception\Command\GenerateWPRestController 15 | - Codeception\Command\GenerateWPRestPostTypeController 16 | - Codeception\Command\GenerateWPAjax 17 | - Codeception\Command\GenerateWPCanonical 18 | - Codeception\Command\GenerateWPXMLRPC 19 | params: 20 | - .env 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "richardtape/shared-reusable-blocks", 3 | "description": "Share reusable blocks across a multisite network", 4 | "type": "wordpress-plugin", 5 | "license": "GPL-2.0-or-later", 6 | "authors": [ 7 | { 8 | "name": "Richard Tape", 9 | "email": "richard.tape@ubc.ca" 10 | } 11 | ], 12 | "minimum-stability": "dev", 13 | "require": {}, 14 | "require-dev": { 15 | "lucatume/wp-browser": "^2.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /inc/class-clone-blocks.php: -------------------------------------------------------------------------------- 1 | register_hooks(); 25 | 26 | }//end init() 27 | 28 | /** 29 | * Calls our methods to register actions and filters. 30 | * 31 | * @return void 32 | */ 33 | public function register_hooks() { 34 | 35 | $this->register_actions(); 36 | 37 | }//end register_hooks() 38 | 39 | /** 40 | * Register the actions we need to hook into the WP ecosystem. 41 | * 42 | * @return void 43 | */ 44 | public function register_actions() { 45 | 46 | // When a spoke site adds a hub, grab the reusable blocks from that hub. 47 | add_action( 'updated_option', array( $this, 'updated_option__clone_hub_blocks_to_spoke' ), 30, 3 ); 48 | 49 | // When a new block is added on a hub, push it to the spokes. 50 | add_action( 'rest_after_insert_wp_block', array( $this, 'rest_after_insert_wp_block__push_block_to_spoke' ), 20, 3 ); 51 | 52 | // When a block is edited on the hub, push changes to the spoke. 53 | add_action( 'rest_after_insert_wp_block', array( $this, 'rest_after_insert_wp_block__edit_block_on_spoke' ), 30, 3 ); 54 | 55 | }//end register_actions() 56 | 57 | 58 | /** 59 | * When a spoke site adds a hub, we clone the reusable blocks from the hub into 60 | * the spoke. Each reusable block is a post of post type wp_block. We copy them over 61 | * and then register an association as post meta as the post IDs would highly likely 62 | * be different. This allows us to update this post in the future when a reusable block 63 | * is updated/deleted on the hub. 64 | * 65 | * @param string $option The option being changed. 66 | * @param string $old_value The previous value of the option. 67 | * @param string $value The new value of the option. 68 | * @return void 69 | */ 70 | public function updated_option__clone_hub_blocks_to_spoke( $option, $old_value, $value ) { 71 | 72 | // Ensure we only do this if it's our option that is being saved. 73 | if ( 'srb_use_as_hubs' !== $option ) { 74 | return; 75 | } 76 | 77 | if ( ! is_admin() ) { 78 | return; 79 | } 80 | 81 | // If no hub is selected then there'll be 1 item in the array that is empty. 82 | if ( ! is_array( $value ) || ! isset( $value[0] ) || ( 1 === count( $value ) && empty( $value[0] ) ) ) { 83 | 84 | // @TODO Ask what to do here; remove all imported reusable blocks? 85 | file_put_contents( WP_CONTENT_DIR . '/debug.log', print_r( array( 'all hubs removed. currently leaving any imported reusable blocks' ), true ), FILE_APPEND ); 86 | return; 87 | } 88 | 89 | // We have at least one hub. We'll now loop over the hubs to get a list of the reusable 90 | // blocks available on each hub. 91 | // Each block post is created on the spoke and post meta is attached to indicate the 92 | // site ID of the hub this came from and the post ID on the hub. 93 | $data_to_import = array(); 94 | 95 | foreach ( $value as $id => $hub_site_id ) { 96 | 97 | // Get the base URL so we can make a REST request. 98 | $rest_url = get_rest_url( absint( $hub_site_id ), 'wp/v2/blocks/' ); 99 | $args = array(); 100 | 101 | // Allow us to work locally on self-signed certs. 102 | if ( defined( 'SRB_DO_NOT_VERIFY_SSL' ) && true === constant( 'SRB_DO_NOT_VERIFY_SSL' ) ) { 103 | $args['sslverify'] = false; 104 | } 105 | 106 | $response = wp_remote_get( esc_url_raw( $rest_url ), $args ); 107 | $api_response = json_decode( wp_remote_retrieve_body( $response ), true ); 108 | 109 | // Nothing to get? Move on. 110 | if ( ! $api_response || ! is_array( $api_response ) ) { 111 | continue; 112 | } 113 | 114 | $data_to_import[ $hub_site_id ] = $api_response; 115 | 116 | } 117 | 118 | $helpers = new Helpers(); 119 | 120 | // Now loop over $data_to_import which contains all the data to import keyed by the hub site ID. 121 | foreach ( $data_to_import as $hub_id => $reusable_blocks_json_array ) { 122 | 123 | foreach ( $reusable_blocks_json_array as $uid => $reusable_block ) { 124 | 125 | $new_post_id = $helpers->create_reusable_block_on_spoke( $reusable_block, $hub_id ); 126 | 127 | // Now detect if this is a block with media, so we can load that. 128 | $parsed_blocks = parse_blocks( $reusable_block['content']['raw'] ); 129 | 130 | if ( ! is_array( $parsed_blocks ) ) { 131 | continue; 132 | } 133 | 134 | foreach ( $parsed_blocks as $pid => $block ) { 135 | $block_name = $block['blockName']; 136 | } 137 | } 138 | } 139 | 140 | }//end updated_option__clone_hub_blocks_to_spoke() 141 | 142 | 143 | /** 144 | * When a reusable block is created on a hub, push this to the attached spokes. 145 | * 146 | * @param WP_Post $post which post is being edited. 147 | * @param WP_REST_Request $request - the full REST Request. 148 | * @param bool $creating True when creating a post, false when updating. 149 | * @return void 150 | */ 151 | public function rest_after_insert_wp_block__push_block_to_spoke( $post, $request, $creating ) { 152 | 153 | // Only do this operation when creating. We have a separate method for editing. 154 | if ( 1 !== absint( $creating ) ) { 155 | return; 156 | } 157 | 158 | // Which site was the block just created on? 159 | $hub_id = get_current_blog_id(); 160 | 161 | $helpers = new Helpers(); 162 | 163 | // Is this site a hub? 164 | if ( ! $helpers->site_is_a_hub( $hub_id ) ) { 165 | return; 166 | } 167 | 168 | // Which spokes are attached to this hub? 169 | $spoke_sites = $helpers->get_spoke_sites_for_hub( $hub_id ); 170 | 171 | if ( ! is_array( $spoke_sites ) || empty( $spoke_sites ) ) { 172 | return; 173 | } 174 | 175 | // Create the data we need to create the reusable block on the spoke. 176 | $reusable_block = $helpers->create_reusable_block_data_from_post_object( $post ); 177 | 178 | // This hub has spoke sites. Let's loop over each spoke site and push 179 | // this new block to each spoke. 180 | foreach ( $spoke_sites as $id => $spoke_site_id ) { 181 | switch_to_blog( $spoke_site_id ); 182 | $helpers->create_reusable_block_on_spoke( $reusable_block, $hub_id ); 183 | restore_current_blog(); 184 | } 185 | 186 | }//end rest_after_insert_wp_block__push_block_to_spoke() 187 | 188 | 189 | /** 190 | * When a block is edited on a hub, we have to push those changes to the spoke sites. The first edit 191 | * happens just after a block is created - when the user adds a title. Each block created as a 192 | * clone on a spoke has post meta which relates it to the hub which created it (Saves the hub ID and 193 | * the post ID of the block on the hub). We then work out which post on the spoke relates to the 194 | * block just updated on the hub and then update that post on the spoke with the new edits made on 195 | * the hub. 196 | * 197 | * @param WP_Post $post which post is being edited. 198 | * @param WP_REST_Request $request - the full REST Request. 199 | * @param bool $creating True when creating a post, false when updating. 200 | * @return void 201 | */ 202 | public function rest_after_insert_wp_block__edit_block_on_spoke( $post, $request, $creating ) { 203 | 204 | if ( 1 === $creating ) { 205 | return; 206 | } 207 | 208 | // We're editing an already-created block, so we need to ensure to update the 209 | // spoke and not create a new one. 210 | 211 | // Which site was the block just edited on? 212 | $hub_id = get_current_blog_id(); 213 | 214 | $helpers = new Helpers(); 215 | 216 | // Is this site a hub? 217 | if ( ! $helpers->site_is_a_hub( $hub_id ) ) { 218 | return; 219 | } 220 | 221 | // Which spokes are attached to this hub? 222 | $spoke_sites = $helpers->get_spoke_sites_for_hub( $hub_id ); 223 | 224 | if ( ! is_array( $spoke_sites ) || empty( $spoke_sites ) ) { 225 | return; 226 | } 227 | 228 | $edited_block_id = $post->ID; 229 | 230 | // On the spoke, for each block sent from the hub we have 2 pieces of meta. 231 | // One tells us which hub site ID the block originated, and the second 232 | // is what post ID on the hub that the block originated. 233 | foreach ( $spoke_sites as $id => $spoke_site_id ) { 234 | 235 | switch_to_blog( $spoke_site_id ); 236 | 237 | global $wpdb; 238 | 239 | $postids_from_hub = $wpdb->get_results( 240 | $wpdb->prepare( 241 | " 242 | SELECT post_id 243 | FROM $wpdb->postmeta 244 | WHERE meta_key = %s 245 | AND meta_value = %d 246 | ", 247 | sanitize_key( 'srb_from_post' ), 248 | absint( $edited_block_id ) 249 | ) 250 | ); 251 | 252 | if ( ! is_array( $postids_from_hub ) || empty( $postids_from_hub ) ) { 253 | restore_current_blog(); 254 | return; 255 | } 256 | 257 | $usable_ids = array(); 258 | foreach ( $postids_from_hub as $oid => $object_with_pid ) { 259 | $usable_ids[] = $object_with_pid->post_id; 260 | } 261 | 262 | $csv_of_postids = implode( ',', $usable_ids ); 263 | 264 | $post_id_on_spoke = $wpdb->get_var( 265 | $wpdb->prepare( 266 | " 267 | SELECT post_id 268 | FROM $wpdb->postmeta 269 | WHERE meta_key = %s 270 | AND meta_value = %d 271 | AND post_id IN (%s) 272 | ", 273 | sanitize_key( 'srb_from_site' ), 274 | absint( $hub_id ), 275 | trim( $csv_of_postids ) 276 | ) 277 | ); 278 | 279 | if ( ! $post_id_on_spoke ) { 280 | restore_current_blog(); 281 | return; 282 | } 283 | 284 | // Now we have the post ID on the spoke for the block which has been edited on the hub. 285 | // Update that post. 286 | wp_update_post( 287 | array( 288 | 'ID' => $post_id_on_spoke, 289 | 'post_title' => $post->post_title, 290 | 'post_content' => $post->post_content, 291 | 'post_name' => $post->post_name, 292 | ) 293 | ); 294 | 295 | restore_current_blog(); 296 | } 297 | 298 | }//end rest_after_insert_wp_block__edit_block_on_spoke() 299 | 300 | }//end class 301 | -------------------------------------------------------------------------------- /inc/class-helpers.php: -------------------------------------------------------------------------------- 1 | get_results( 28 | $wpdb->prepare( 29 | " 30 | SELECT blog_id 31 | FROM $wpdb->blogmeta 32 | WHERE meta_key = %s 33 | AND meta_value = %d 34 | ", 35 | $meta_key, 36 | $meta_value 37 | ) 38 | ); 39 | 40 | if ( ! $hubs || ! is_array( $hubs ) ) { 41 | return array(); 42 | } 43 | 44 | $hub_sites = array(); 45 | 46 | foreach ( $hubs as $id => $hub ) { 47 | $hub_sites[] = $hub->blog_id; 48 | } 49 | 50 | return $hub_sites; 51 | 52 | }//end fetch_hub_sites() 53 | 54 | 55 | /** 56 | * Determine if a site is a hub or not. 57 | * 58 | * @param int $site_id Which site to test for b eing a hub. 59 | * @return bool True if site is set as a hub, false otherwise. 60 | */ 61 | public function site_is_a_hub( $site_id ) { 62 | 63 | $site_id = absint( $site_id ); 64 | 65 | $hubs = $this->fetch_hub_sites(); 66 | 67 | if ( ! in_array( $site_id, array_values( $hubs ), false ) ) { 68 | return false; 69 | } 70 | 71 | return true; 72 | 73 | }//end site_is_a_hub() 74 | 75 | 76 | /** 77 | * For the passed $hub_id return an array of site IDs for all the spokes 78 | * attached to this hub. 79 | * 80 | * @param int $hub_id The site ID of the site we are retrieving the spokes for. 81 | * @return array The site IDs for the spokes attached to the passed $hub_id 82 | */ 83 | public function get_spoke_sites_for_hub( $hub_id ) { 84 | 85 | $hub_id = absint( $hub_id ); 86 | 87 | global $wpdb; 88 | 89 | $meta_key = 'is_a_hub_for_site'; 90 | 91 | $spokes = $wpdb->get_results( 92 | $wpdb->prepare( 93 | " 94 | SELECT meta_value 95 | FROM $wpdb->blogmeta 96 | WHERE meta_key = %s 97 | AND blog_id = %d 98 | ", 99 | $meta_key, 100 | $hub_id 101 | ) 102 | ); 103 | 104 | if ( ! $spokes || ! is_array( $spokes ) ) { 105 | return array(); 106 | } 107 | 108 | $spoke_sites = array(); 109 | 110 | foreach ( $spokes as $id => $spoke ) { 111 | $spoke_sites[] = $spoke->meta_value; 112 | } 113 | 114 | return $spoke_sites; 115 | 116 | }//end get_spoke_sites_for_hub() 117 | 118 | /** 119 | * Create a reusable block post on the spoke and relate it to the post on the hub. 120 | * 121 | * @param array $block_json A JSON array containing the details of the block. 122 | * @param int $hub_id The site ID on which the block is to be created. 123 | * @return int New Post ID 124 | */ 125 | public function create_reusable_block_on_spoke( $block_json = array(), $hub_id ) { 126 | 127 | // Insert post. 128 | $post_args = array( 129 | 'post_content' => $block_json['content']['raw'], 130 | 'post_title' => $block_json['title']['raw'], 131 | 'post_type' => 'wp_block', 132 | 'post_status' => $block_json['status'], 133 | ); 134 | 135 | // @TODO: Test if we already have this post on the spoke. Look for a post with post meta 136 | // key "srb_from_post" equal to $block_json['id'] 137 | global $wpdb; 138 | 139 | // First test if we have ANY posts from this hub, if we don't, then we're safe to add. 140 | $hub_query = $wpdb->prepare( "SELECT COUNT(meta_value) FROM $wpdb->postmeta WHERE meta_key ='srb_from_site' and meta_value = %d", absint( $hub_id ) ); 141 | $num_of_posts_from_this_hub = $wpdb->get_col( $hub_query ); 142 | 143 | if ( $num_of_posts_from_this_hub && $num_of_posts_from_this_hub > 0 ) { 144 | 145 | // We have posts already from this hub. Test if we have this specific post. 146 | $posts_with_this_post_id_from_a_hub = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key ='srb_from_post' and meta_value = %d", absint( $block_json['id'] ) ); 147 | $post_id_on_spoke_where_post_from_spoke_matches_current_post_id = $wpdb->get_results( $posts_with_this_post_id_from_a_hub ); 148 | 149 | if ( $post_id_on_spoke_where_post_from_spoke_matches_current_post_id && ! empty( $post_id_on_spoke_where_post_from_spoke_matches_current_post_id ) ) { 150 | // We have this block on the spoke, so bail. 151 | return $post_id_on_spoke_where_post_from_spoke_matches_current_post_id; 152 | } 153 | 154 | } 155 | 156 | $new_post_id = wp_insert_post( $post_args ); 157 | 158 | // Now add meta to show which post ID and Blog ID this came from. 159 | add_post_meta( $new_post_id, 'srb_from_site', absint( $hub_id ) ); 160 | add_post_meta( $new_post_id, 'srb_from_post', absint( $block_json['id'] ) ); 161 | 162 | return absint( $new_post_id ); 163 | 164 | }//end create_reusable_block_on_spoke() 165 | 166 | 167 | /** 168 | * Create the reusable block data we need to create a reusable block 169 | * from a WP_Post object. 170 | * 171 | * @param WP_Post $post The post object from which we create the json object. 172 | * @return array 173 | */ 174 | public function create_reusable_block_data_from_post_object( $post ) { 175 | 176 | $reusable_block = array( 177 | 'status' => $post->post_status, 178 | 'id' => $post->ID, 179 | 'content' => array( 180 | 'raw' => $post->post_content, 181 | ), 182 | 'title' => array( 183 | 'raw' => $post->post_title, 184 | ), 185 | ); 186 | 187 | return $reusable_block; 188 | 189 | }//end create_reusable_block_data_from_post_object() 190 | 191 | }//end class 192 | -------------------------------------------------------------------------------- /inc/class-options.php: -------------------------------------------------------------------------------- 1 | register_hooks(); 26 | 27 | }// end init() 28 | 29 | /** 30 | * Calls our methods to register actions and filters. 31 | * 32 | * @return void 33 | */ 34 | public function register_hooks() { 35 | 36 | $this->register_actions(); 37 | $this->register_filters(); 38 | 39 | }// end register_hooks() 40 | 41 | /** 42 | * Register the actions we need to hook into the WP ecosystem. 43 | * 44 | * @return void 45 | */ 46 | public function register_actions() { 47 | 48 | /* Hub Options */ 49 | /* *********** */ 50 | // Register the option to say if a site should act as a hub or not 51 | add_action( 'admin_init', array( $this, 'admin_init__register_hub_option' ) ); 52 | 53 | // When the srb_site_is_hub option is saved, hook in and add/remove blogmeta 54 | add_action( 'updated_option', array( $this, 'updated_option__srb_site_is_hub' ), 20, 3 ); 55 | 56 | /* Spoke Options */ 57 | /* ************* */ 58 | // Register the option to allow a spoke site to add one or more hubs. 59 | add_action( 'admin_init', array( $this, 'admin_init__register_spoke_hub_choice' ) ); 60 | 61 | // When a spoke site chooses (a) hub(s), ensure the hub site knows about it 62 | add_action( 'updated_option', array( $this, 'updated_option__srb_use_as_hubs' ), 20, 3 ); 63 | 64 | }// end register_actions() 65 | 66 | 67 | /** 68 | * Register our filters. 69 | * 70 | * @return void 71 | */ 72 | public function register_filters() { 73 | 74 | }// end register_filters() 75 | 76 | 77 | /** 78 | * Register the option to allow admins of the current site to stipulate that this 79 | * site will be used as a 'hub' for shared reusable blocks. Option appears in the 80 | * settings > writing panel as a checkbox. 81 | * 82 | * @return void 83 | */ 84 | public function admin_init__register_hub_option() { 85 | 86 | // Admins of this site only 87 | if ( ! current_user_can( 'manage_options' ) ) { 88 | return; 89 | } 90 | 91 | // What is the key used to store our option? 92 | $option_key = 'srb_site_is_hub'; 93 | 94 | // Which setting screen is it shown on? 95 | $screen = 'writing'; 96 | 97 | // Register the field... 98 | register_setting( 99 | $screen, 100 | $option_key, 101 | array( 102 | 'type' => 'integer', 103 | 'sanitize_callback' => 'absint', 104 | 'default' => 0, 105 | ) 106 | ); 107 | 108 | // ... and now add it. 109 | add_settings_field( 110 | $option_key, 111 | __( 'Shared Reusable Blocks Hub?', 'srb' ), 112 | array( $this, 'hub_option_markup' ), 113 | $screen, 114 | 'default', 115 | array( 116 | 'label_for' => $option_key, 117 | ) 118 | ); 119 | 120 | }// end admin_init__register_hub_option() 121 | 122 | /** 123 | * Outputs the checkbox field for saying a site is a hub 124 | * 125 | * @return void 126 | */ 127 | public function hub_option_markup() { 128 | 129 | $is_hub = absint( get_option( 'srb_site_is_hub' ) ); 130 | $help_text = __( 'Should the reusable blocks on this site be available on other sites which specifically choose to use this site as a hub? i.e. should this site act as a shared reusable blocks hub?', 'srb' ); 131 | ?> 132 | 133 | /> 134 | 135 | writing screen to allow an admin to 180 | * select which sites they wish to use as a hub for shared reusable blocks. 181 | * 182 | * @return void 183 | */ 184 | public function admin_init__register_spoke_hub_choice() { 185 | 186 | // Admins of this site only 187 | if ( ! current_user_can( 'manage_options' ) ) { 188 | return; 189 | } 190 | 191 | // Check that we have at least one hub registered. 192 | if ( ! $this->at_least_one_hub_registered() ) { 193 | return; 194 | } 195 | 196 | // What is the key used to store our option? 197 | $option_key = 'srb_use_as_hubs'; 198 | 199 | // Which setting screen is it shown on? 200 | $screen = 'writing'; 201 | 202 | // Register the field... 203 | register_setting( 204 | $screen, 205 | $option_key, 206 | array( 207 | 'type' => 'string', 208 | 'default' => '', 209 | ) 210 | ); 211 | 212 | // ... and now add it. 213 | add_settings_field( 214 | $option_key, 215 | __( 'Use other site as hub?', 'srb' ), 216 | array( $this, 'spoke_option_markup' ), 217 | $screen, 218 | 'default', 219 | array( 220 | 'label_for' => $option_key, 221 | ) 222 | ); 223 | 224 | }// end admin_init__register_spoke_hub_choice() 225 | 226 | 227 | /** 228 | * Markup for the option which allows an admin to select other sites to use as 229 | * a hub for shared reusable blocks. 230 | * 231 | * @return void 232 | */ 233 | public function spoke_option_markup() { 234 | 235 | $use_hubs = get_option( 'srb_use_as_hubs' ); 236 | $help_text = __( 'From which hub site(s) would you like to fetch reusable blocks?', 'srb' ); 237 | 238 | if ( ! is_array( $use_hubs ) ) { 239 | $use_hubs = array(); 240 | } 241 | 242 | $helpers = new Helpers(); 243 | $hub_sites = $helpers->fetch_hub_sites(); 244 | 245 | $options = ''; 246 | 247 | foreach ( $hub_sites as $id => $blog_id ) { 248 | 249 | $blog_id = absint( $blog_id ); 250 | 251 | // Don't do anything if the current blog has set itself up as a hub, too 252 | if ( absint( get_current_blog_id() ) === $blog_id ) { 253 | continue; 254 | } 255 | 256 | $blog_name = get_blog_option( $blog_id, 'blogname', $blog_id ); 257 | $selected = ( in_array( $blog_id, array_values( $use_hubs ), false ) ) ? 'selected' : ''; 258 | $options .= ""; 259 | } 260 | 261 | ?> 262 | 263 | 267 | 268 | remove_spoke_site_from_all_hubs( get_current_blog_id() ); 303 | return; 304 | } 305 | 306 | // There are hubs selected 307 | // Remove all existing hubs for this spoke 308 | $this->remove_spoke_site_from_all_hubs( get_current_blog_id() ); 309 | 310 | // And now add back all the ones that have just been asked for. 311 | foreach ( $value as $id => $hub_site_id ) { 312 | add_site_meta( $hub_site_id, $option_key_on_hub, get_current_blog_id() ); 313 | } 314 | 315 | }// end updated_option__srb_use_as_hubs() 316 | 317 | 318 | 319 | 320 | /** 321 | * A spoke site has updated and chosen to have no hubs. We look in the 322 | * blogmeta table and remove all references 323 | * 324 | * @param int $spoke_site_id 325 | * @return void 326 | */ 327 | public function remove_spoke_site_from_all_hubs( $spoke_site_id = null ) { 328 | 329 | global $wpdb; 330 | 331 | $meta_key = 'is_a_hub_for_site'; 332 | $meta_value = absint( $spoke_site_id ); 333 | 334 | $wpdb->delete( 335 | $wpdb->blogmeta, 336 | array( 337 | 'meta_key' => $meta_key, 338 | 'meta_value' => $meta_value, 339 | ), 340 | array( 341 | '%s', 342 | '%d', 343 | ) 344 | ); 345 | 346 | }// end remove_spoke_site_from_all_hubs() 347 | 348 | 349 | /** 350 | * Helper method to determine if there is at least one hub registered. 351 | * 352 | * @return bool true if one or more hubs registered, false otherwise. 353 | */ 354 | public function at_least_one_hub_registered() { 355 | 356 | $helpers = new Helpers(); 357 | $hubs = $helpers->fetch_hub_sites(); 358 | 359 | if ( $hubs && count( $hubs ) >= 1 ) { 360 | return true; 361 | } 362 | 363 | return false; 364 | 365 | }// end at_least_one_hub_registered() 366 | 367 | }// end class Options 368 | -------------------------------------------------------------------------------- /inc/class-rest-api.php: -------------------------------------------------------------------------------- 1 | register_hooks(); 25 | 26 | }//end init() 27 | 28 | /** 29 | * Calls our methods to register actions and filters. 30 | * 31 | * @return void 32 | */ 33 | public function register_hooks() { 34 | 35 | $this->register_actions(); 36 | $this->register_filters(); 37 | 38 | }//end register_hooks() 39 | 40 | /** 41 | * Register the actions we need to hook into the WP ecosystem. 42 | * 43 | * @return void 44 | */ 45 | public function register_actions() { 46 | 47 | }//end register_actions() 48 | 49 | /** 50 | * Register our filters. 51 | * 52 | * @return void 53 | */ 54 | public function register_filters() { 55 | 56 | add_filter( 'register_post_type_args', array( $this, 'register_post_type_args__add_reusable_blocks_to_rest_api' ), 99, 2 ); 57 | 58 | }//end register_filters() 59 | 60 | 61 | /** 62 | * Add reusable blocks to the rest API. We do this by registering a new class 63 | * to handle the REST API responses and ensuring we give non-logged-in users the 64 | * ability to read them. 65 | * 66 | * @param array $args Array of arguments for registering a post type. 67 | * @param string $post_type Post type key. 68 | * 69 | * @return array The (potentially) modified post type arguments. 70 | */ 71 | public function register_post_type_args__add_reusable_blocks_to_rest_api( $args, $post_type ) { 72 | 73 | if ( 'wp_block' !== $post_type ) { 74 | return $args; 75 | } 76 | 77 | $helpers = new Helpers(); 78 | 79 | // Check that this site is a hub and only add reusable blocks if it is. 80 | $hubs = $helpers->fetch_hub_sites(); 81 | 82 | if ( ! in_array( get_current_blog_id(), $hubs, false ) ) { 83 | return $args; 84 | } 85 | 86 | $args['rest_controller_class'] = '\SharedReusableBlocks\SRB_REST_Block_Controller'; 87 | 88 | return $args; 89 | 90 | }//end register_post_type_args__add_reusable_blocks_to_rest_api() 91 | 92 | }//end class 93 | -------------------------------------------------------------------------------- /inc/class-srb-rest-block-controller.php: -------------------------------------------------------------------------------- 1 | init(); 41 | 42 | require_once 'inc/class-clone-blocks.php'; 43 | $clone_blocks = new \SharedReusableBlocks\Clone_Blocks(); 44 | $clone_blocks->init(); 45 | 46 | if ( ! srb_is_rest() ) { 47 | return; 48 | } 49 | 50 | require_once 'inc/class-rest-api.php'; 51 | require_once 'inc/class-srb-rest-block-controller.php'; 52 | $rest_api = new \SharedReusableBlocks\Rest_API(); 53 | $rest_api->init(); 54 | 55 | }//end load_srb_files() 56 | 57 | /** 58 | * Checks if the current request is a WP REST API request. 59 | * 60 | * Case #1: After WP_REST_Request initialisation 61 | * Case #2: Support "plain" permalink settings 62 | * Case #3: URL Path begins with wp-json/ (your REST prefix) 63 | * Also supports WP installations in subfolders 64 | * 65 | * @return boolean 66 | * @author matzeeable ref https://wordpress.stackexchange.com/a/317041 67 | */ 68 | function srb_is_rest() { 69 | 70 | $prefix = rest_get_url_prefix(); 71 | 72 | if ( defined( 'REST_REQUEST' ) && REST_REQUEST || isset( $_GET['rest_route'] ) && strpos( trim( $_GET['rest_route'], '\\/' ), $prefix, 0 ) === 0 ) { 73 | return true; 74 | } 75 | 76 | // (#3) 77 | $rest_url = wp_parse_url( site_url( $prefix ) ); 78 | $current_url = wp_parse_url( add_query_arg( array() ) ); 79 | 80 | return strpos( $current_url['path'], $rest_url['path'], 0 ) === 0; 81 | 82 | }//end srb_is_rest() 83 | 84 | -------------------------------------------------------------------------------- /tests/_data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardtape/shared-reusable-blocks/9bd803b9e0e150e6267efed3baf7efd15e28215c/tests/_data/.gitkeep -------------------------------------------------------------------------------- /tests/_output/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardtape/shared-reusable-blocks/9bd803b9e0e150e6267efed3baf7efd15e28215c/tests/_output/.gitkeep -------------------------------------------------------------------------------- /tests/_output/TestCept.fail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |57 | Just another WordPress site
58 |Welcome to WordPress. This is your first post. Edit or delete it, then start writing!
82 |
Hi, this is a comment.
124 |122 | To get started with moderating, editing, and deleting comments, please visit the Comments screen in the dashboard.
123 | Commenter avatars come from Gravatar.