Changeset 2864100
- Timestamp:
- 02/13/2023 02:01:35 AM (3 years ago)
- Location:
- wp-super-network
- Files:
-
- 18 added
- 12 edited
- 1 copied
-
tags/1.2.0 (copied) (copied from wp-super-network/trunk)
-
tags/1.2.0/assets (added)
-
tags/1.2.0/assets/js (added)
-
tags/1.2.0/assets/js/post-new.js (added)
-
tags/1.2.0/init.php (modified) (1 diff)
-
tags/1.2.0/readme.txt (modified) (5 diffs)
-
tags/1.2.0/src/blog.php (modified) (1 diff)
-
tags/1.2.0/src/expression.php (added)
-
tags/1.2.0/src/insert.php (added)
-
tags/1.2.0/src/network.php (modified) (18 diffs)
-
tags/1.2.0/src/node.php (added)
-
tags/1.2.0/src/plugin.php (modified) (5 diffs)
-
tags/1.2.0/src/query.php (added)
-
tags/1.2.0/src/subquery.php (added)
-
tags/1.2.0/src/table.php (added)
-
tags/1.2.0/wp-super-network.php (modified) (1 diff)
-
trunk/assets (added)
-
trunk/assets/js (added)
-
trunk/assets/js/post-new.js (added)
-
trunk/init.php (modified) (1 diff)
-
trunk/readme.txt (modified) (5 diffs)
-
trunk/src/blog.php (modified) (1 diff)
-
trunk/src/expression.php (added)
-
trunk/src/insert.php (added)
-
trunk/src/network.php (modified) (18 diffs)
-
trunk/src/node.php (added)
-
trunk/src/plugin.php (modified) (5 diffs)
-
trunk/src/query.php (added)
-
trunk/src/subquery.php (added)
-
trunk/src/table.php (added)
-
trunk/wp-super-network.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
wp-super-network/tags/1.2.0/init.php
r2671848 r2864100 24 24 require_once SUPER_NETWORK_DIR . 'vendor/autoload.php'; 25 25 26 // Load superclasses. 27 require_once SUPER_NETWORK_DIR . 'src/node.php'; 28 26 29 // Load classes. 27 30 require_once SUPER_NETWORK_DIR . 'src/blog.php'; 31 require_once SUPER_NETWORK_DIR . 'src/expression.php'; 28 32 require_once SUPER_NETWORK_DIR . 'src/field.php'; 33 require_once SUPER_NETWORK_DIR . 'src/insert.php'; 29 34 require_once SUPER_NETWORK_DIR . 'src/network.php'; 30 35 require_once SUPER_NETWORK_DIR . 'src/page.php'; 31 36 require_once SUPER_NETWORK_DIR . 'src/plugin.php'; 37 require_once SUPER_NETWORK_DIR . 'src/query.php'; 32 38 require_once SUPER_NETWORK_DIR . 'src/section.php'; 39 require_once SUPER_NETWORK_DIR . 'src/subquery.php'; 40 require_once SUPER_NETWORK_DIR . 'src/table.php'; 33 41 34 42 // Initialize the plugin. -
wp-super-network/tags/1.2.0/readme.txt
r2671848 r2864100 2 2 Contributors: manfcarlo 3 3 Tags: network, multisite, share, sharing, move, migrate, migration, duplicate, syndication, content, management 4 Tested up to: 5.95 Stable tag: 1. 1.04 Tested up to: 6.1 5 Stable tag: 1.2.0 6 6 License: GPLv2 or later 7 7 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 40 40 = How do I republish a page to the whole network? = 41 41 42 As of version 1.0.5, you could flag any post or page on a main site for republication by finding it on the Posts or Pages screen and clicking Republish, but it would not take any effect. Version 1.1.0 now implements the behaviour of this feature! 42 Flag any post or page on a main site for republication by finding it on the Posts or Pages screen and clicking Republish. 43 43 44 44 After you have republished the post or page, it will start showing up across every site on your network. You can also access the network admin and click on Super Network. You will see a list of all posts and pages across all sites in the network that have been flagged for republication. … … 52 52 = How do I turn an existing site into the main site for a new network? = 53 53 54 This feature is s till to come.54 This feature is scheduled for the next major release. 55 55 56 56 = How do I manage all of my sites in a single admin area? = … … 62 62 If two or more sites on a network have a post with the same numeric ID, none of these posts is able to be accessed on consolidated mode. You can eliminate post ID collisions by deleting posts you don't want via the network admin screen > Super Network. 63 63 64 = Why are republished posts and pages not editable in the admin area? =64 = Is this the same as Global Terms? = 65 65 66 This feature is still to come. 66 No! 67 68 WP Super Network does not enable any "global" data relationships. The data you can create with WP Super Network is the same as the data you can create without it. WP Super Network merely brings the management and the viewing of the data together into the one place. 69 70 = Is this the same as WP Multi Network? = 71 72 No! 73 74 WP Super Network is compatible with multi-network environments, but it does not include the management tools that WP Multi Network includes. Therefore, the two plugins have different purposes and can work well together. 75 76 = Is this the same as Distributor? = 77 78 No! 79 80 WP Super Network does not duplicate content between sites, and all posts will always continue to have one and only one permalink. Posts from across the network may preview in archives, but their permalinks will always direct back to their original respective sites. 81 82 = Is this the same as MainWP? = 83 84 Only a little bit. 85 86 WP Super Network and MainWP share a few similar features. However, MainWP is only a good fit for users who don't use the block editor and don't use multisite, since MainWP is yet to announce any support for the block editor and MainWP's own documentation states that it does not test or support multisite. This is despite the block editor and multisite both being core WordPress features for a significant number of years. 87 88 Also, MainWP's features do not integrate with the core WordPress interface and can only be accessed through its own specially-built interface. 89 90 Although WP Super Network requires multisite, it does not require you to learn a new interface. Instead, you can continue working with the core WordPress interfaces you are already familiar with, including both the block editor and classic editor. 91 92 = Which plugins are incompatible with WP Super Network? = 93 94 WP Super Network is incompatible with [Link Manager](https://wordpress.org/plugins/link-manager/). Users of Link Manager will not be supported and are advised to not install WP Super Network. 95 96 WP Super Network is currently incompatible with [SQLite Database Integration](https://wordpress.org/plugins/sqlite-database-integration/). Users of SQLite Database Integration are advised to not install WP Super Network for the time being. Compatibility is planned for a later release, so check back later. 97 98 If you use a plugin that adds a db.php drop-in, it may or may not be incompatible with WP Super Network. To know if you have a db.php drop-in, go to the Plugins screen, click on Drop-in and check if db.php is listed. If the db.php drop-in was added by a plugin that follows the WordPress conventions well and has high user ratings, you are unlikely to see any incompatibility issues with WP Super Network, but you are welcome to ask on the support forum. 99 100 = Is WP Super Network safe to install? = 101 102 See above question about incompatible plugins. 103 104 For best results, the free version of WP Super Network should be activated on a fresh network. While results will vary, you may find that some features (e.g. consolidated mode) are limited in utility for existing networks and/or cause a slow-down in performance for large networks. 105 106 None the less, the utmost care has been taken to avoid permanent data loss or corruption. Keeping regular database back-ups is always recommended, but deactivating the plugin should return your network back to its previous state. 107 108 A premium version is planned for the future, focusing on enhanced performance for medium-to-large networks. 67 109 68 110 = How do I suggest a new feature or submit a bug report? = … … 71 113 72 114 == Changelog == 115 116 = 1.2.0 = 117 * Republished posts and pages can be inserted, updated and deleted 118 * Availability of taxonomies and comments for republished posts 119 * Changed behaviour of select queries that target a specific post 120 * Cache implemented for duplicate queries 121 * Fixed a few bugs with network-based options 122 * Minimum PHP version lifted to 7.2 73 123 74 124 = 1.1.0 = -
wp-super-network/tags/1.2.0/src/blog.php
r2671848 r2864100 38 38 return $this->wp_site->site_id; 39 39 } 40 41 if ( $key === 'name' ) 42 { 43 return $this->wp_site->blogname; 44 } 40 45 } 41 46 -
wp-super-network/tags/1.2.0/src/network.php
r2671848 r2864100 7 7 class Network 8 8 { 9 const ID_COLS = array(10 'comments' => 'comment_post_ID',11 'postmeta' => 'post_id',12 'posts' => 'ID',13 'term_relationships' => 'object_id'14 );15 16 9 /** 17 10 * Supernetwork … … 54 47 private $page; 55 48 56 private $parser; 57 private $creator; 58 59 private $collisions = array(); 49 /** 50 * Keeps track of ID collisions. 51 * 52 * @since 1.2.0 53 * @var array 54 */ 55 private $collisions = WP_Super_Network::ENTITIES_TO_REPLACE; 56 57 /** 58 * Cache that maps ID's to blogs. 59 * 60 * @since 1.2.0 61 * @var array 62 */ 63 private $blog_cache = WP_Super_Network::ENTITIES_TO_REPLACE; 64 60 65 private $republished = array(); 61 66 private $post_types = array(); 62 67 68 private $meta_ids = array(); 69 63 70 public function shared_auto_increment( $post_ID, $post, $update ) 64 71 { … … 69 76 } 70 77 71 private function set_auto_increment( $new ) 78 public function auto_increment_comments( $id, $comment ) 79 { 80 if ( $this->consolidated || in_array( (string) $comment->comment_post_ID, $this->republished, true ) ) 81 { 82 $this->set_auto_increment( $id + 1, 'comments' ); 83 } 84 } 85 86 public function auto_increment_terms( $term_id, $tt_id, $taxonomy, $args ) 87 { 88 if ( $this->consolidated ) 89 { 90 $this->set_auto_increment( $tt_id + 1, 'term_taxonomy' ); 91 $this->set_auto_increment( $term_id + 1, 'terms' ); 92 } 93 } 94 95 public function auto_increment_term_relationships( $object_id, $tt_id, $taxonomy ) 96 { 97 if ( !$this->consolidated && in_array( (string) $object_id, $this->republished, true ) ) 98 { 99 $this->set_auto_increment( $tt_id + 1, 'term_taxonomy' ); 100 $term = get_term_by( 'term_taxonomy_id', $tt_id ) and $this->set_auto_increment( $term->term_id + 1, 'terms' ); 101 } 102 } 103 104 private function set_auto_increment( $new, $entity = 'posts' ) 72 105 { 73 106 foreach ( $this->blogs as $blog ) 74 107 { 75 if ( $new > (int) $GLOBALS['wpdb']->get_var( ' select auto_increment from information_schema.tables where table_schema = \'' . DB_NAME . '\' and table_name = \'' . $blog->table( 'posts') . '\'' ) )76 { 77 $GLOBALS['wpdb']->query( ' alter table ' . $blog->table( 'posts' ) . ' auto_increment= ' . (string) $new );108 if ( $new > (int) $GLOBALS['wpdb']->get_var( 'SELECT `auto_increment` FROM `information_schema`.`tables` WHERE `table_schema` = \'' . DB_NAME . '\' AND `table_name` = \'' . $blog->table( $entity ) . '\'' ) ) 109 { 110 $GLOBALS['wpdb']->query( 'ALTER TABLE `' . $blog->table( $entity ) . '` AUTO_INCREMENT = ' . (string) $new ); 78 111 } 79 112 } … … 96 129 return $this->republished; 97 130 } 98 } 99 100 public function __set( $key, $value ) 101 { 102 if ( $key === 'consolidated' ) 103 { 104 if ( $value === true ) 105 { 106 $this->consolidated = true; 107 } 108 109 if ( $value === false ) 110 { 111 $this->consolidated = false; 112 113 if ( !empty( $this->republished ) ) 114 { 115 $this->set_auto_increment( $this->republished[0] + 1 ); 116 } 117 } 118 } 119 } 120 121 private function union( $table ) 122 { 123 if ( !$this->consolidated && empty( $this->republished ) ) 131 132 if ( $key === 'post_types' ) 133 { 134 return $this->post_types; 135 } 136 } 137 138 public function union( $table ) 139 { 140 // No need to do anything if not consolidated and no republished posts. 141 if ( !$this->consolidated && ( empty( $this->republished ) || empty( $post_cols = array_keys( WP_Super_Network::TABLES_TO_REPLACE[ $table ], 'posts', true ) ) ) ) 124 142 { 125 143 return $GLOBALS['wpdb']->__get( $table ); … … 131 149 { 132 150 $where = array(); 133 134 if ( $this->consolidated ) 135 { 136 if ( !empty( $this->collisions ) ) 137 { 138 $where[] = self::ID_COLS[ $table ] . ' not in (' . implode( ', ', $this->collisions ) . ')'; 139 } 140 141 if ( $table === 'posts' && !empty( $this->post_types ) && !$blog->is_network() ) 142 { 143 $where[] = 'post_type not in (\'' . implode( '\', \'', $this->post_types ) . '\')'; 144 } 145 } 146 else 147 { 151 $this->exclude( $where, $table, $blog ); 152 153 if ( !$this->consolidated ) 154 { 155 // Add republished posts. 148 156 if ( $blog->table( $table ) !== $GLOBALS['wpdb']->__get( $table ) ) 149 157 { 150 $where[] = self::ID_COLS[ $table ] . ' in (' . implode( ', ', $this->republished ) . ')'; 158 foreach ( $post_cols as $col ) 159 { 160 // Post parent is currently not being handled. 161 $col === 'post_parent' or $where[] = '`' . $col . '` IN (' . implode( ', ', $this->republished ) . ')'; 162 } 151 163 } 152 164 } 153 165 154 $tables[] = 'select * from ' . $blog->table( $table ) . ( empty( $where ) ? '' : ( ' where ' . implode( ' and ', $where ) ) ); 155 } 156 157 return implode( ' union ', $tables ); 166 $tables[] = 'SELECT * FROM `' . $blog->table( $table ) . ( empty( $where ) ? '`' : ( '` WHERE ' . implode( ' AND ', $where ) ) ); 167 } 168 169 return implode( ' UNION ALL ', $tables ); 170 } 171 172 public function exclude( &$where, $table, $blog, $alias = '' ) 173 { 174 if ( $this->consolidated ) 175 { 176 empty( $alias ) or $alias .= '.'; 177 178 // Exclude any entities involved in collisions. 179 foreach ( array_keys( $this->collisions ) as $entity ) 180 { 181 if ( !empty( $this->collisions[ $entity ] ) ) 182 { 183 foreach ( array_keys( WP_Super_Network::TABLES_TO_REPLACE[ $table ], $entity, true ) as $col ) 184 { 185 $where[] = $alias . '`' . $col . '` NOT IN (' . implode( ', ', $this->collisions[ $entity ] ) . ')'; 186 } 187 } 188 } 189 190 // Exclude network-based post types. 191 if ( $table === 'posts' && !empty( $this->post_types ) && !$blog->is_network() ) 192 { 193 $where[] = $alias . '`post_type` NOT IN (\'' . implode( '\', \'', $this->post_types ) . '\')'; 194 } 195 } 158 196 } 159 197 160 198 public function report_collisions() 161 199 { 162 if ( $this->consolidated && !empty( $this->collisions ) && current_user_can( 'manage_network_options' ) ) 163 { 164 echo '<div class="notice notice-error"><p><strong>WP Super Network detected ' . count( $this->collisions ) . ' post ID collisions</strong> across your network! These posts have been temporarily hidden. To access them again, please turn off consolidated mode.</p></div>'; 200 $term_collisions = count( $this->collisions['term_taxonomy'] ) + count( $this->collisions['terms'] ); 201 202 if ( $this->consolidated && ( !empty( $this->collisions['posts'] ) || $term_collisions > 0 || !empty( $this->collisions['comments'] ) ) && current_user_can( 'manage_network_options' ) ) 203 { 204 echo '<div class="notice notice-error"><p><strong>WP Super Network detected '; 205 206 echo empty( $this->collisions['posts'] ) ? '' : count( $this->collisions['posts'] ) . ' post ID collisions'; 207 208 if ( !empty( $this->collisions['posts'] ) && $term_collisions > 0 ) 209 { 210 echo empty( $this->collisions['comments'] ) ? ' and ' : ', '; 211 } 212 213 echo $term_collisions > 0 ? $term_collisions . ' taxonomy term ID collisions' : ''; 214 215 if ( !empty( $this->collisions['comments'] ) ) 216 { 217 echo ( !empty( $this->collisions['posts'] ) || $term_collisions > 0 ) ? ' and ' : ''; 218 echo count( $this->collisions['comments'] ) . ' comment ID collisions'; 219 } 220 221 echo '</strong> across your network! These entities have been temporarily hidden. To access them again, please turn off consolidated mode.</p></div>'; 165 222 } 166 223 } … … 169 226 * Constructor. 170 227 * 171 * Constructs the network.228 * Queries sent during this method do not get modified, as the filter is applied later. 172 229 * 173 230 * @since 1.0.4 … … 179 236 foreach ( get_sites( 'network_id=' . $network->id ) as $site ) 180 237 { 181 array_push( $this->blogs, new Blog( $site ) ); 182 } 183 184 add_filter( 'admin_footer', array( $this, 'report_collisions' ) ); 185 186 $this->parser = new \PHPSQLParser\PHPSQLParser(); 187 $this->creator = new \PHPSQLParser\PHPSQLCreator(); 188 189 $this->collisions = $GLOBALS['wpdb']->get_col( 'select ID from (' . $this->union( 'posts' ) . ') posts group by ID having count(*) > 1 order by ID asc' ); 190 $this->republished = $GLOBALS['wpdb']->get_col( 'select post_id from (' . $this->union( 'postmeta' ) . ') postmeta where meta_key = \'_supernetwork_share\' order by post_id desc' ); 191 $this->post_types = array_keys( (array) get_option( 'supernetwork_post_types' ) ); 238 $this->blogs[ (int) $site->blog_id ] = new Blog( $site ); 239 } 192 240 193 241 $this->page = new Settings_Page( … … 209 257 210 258 global $wpdb; 211 $results = $wpdb->get_results( " select distinct option_name from $wpdb->options where option_name not like '\_%' and option_name not like 'supernetwork\_%' order by option_name" );259 $results = $wpdb->get_results( "SELECT DISTINCT `option_name` FROM $wpdb->options WHERE `option_name` NOT LIKE '\_%' AND `option_name` NOT LIKE 'supernetwork\_%' ORDER BY `option_name`" ); 212 260 $labels = array(); 213 261 … … 252 300 'Consolidated Mode', 253 301 'Turn on consolidated mode?', 254 '<strong>Warning:</strong> Consolidated mode is highly unstable. It is strongly suggested that you have a REGULAR backup regime in place for your database, before activatingconsolidated mode.'302 '<strong>Warning:</strong> Consolidated mode is recommended for fresh networks. If you have pre-existing data (e.g. posts and pages) on your network, some of it may not be compatible with consolidated mode.' 255 303 ); 256 304 … … 263 311 } 264 312 313 public function add_new_post( $hook_suffix ) 314 { 315 $post_type = empty( $_GET['post_type'] ) ? 'post' : $_GET['post_type']; 316 317 if ( $hook_suffix === 'edit.php' && $this->consolidated && !in_array( $post_type, $this->post_types, true ) ) 318 { 319 wp_enqueue_script( 320 'supernetwork-post-new', 321 SUPER_NETWORK_URL . 'assets/js/post-new.js', 322 array(), 323 null, 324 true 325 ); 326 327 $blogs = array(); 328 $type = get_post_type_object( $post_type ); 329 $edit = isset( $post_type ) ? $type->cap->edit_posts : 'do_not_allow'; 330 $create = isset( $post_type ) ? $type->cap->create_posts : 'do_not_allow'; 331 332 foreach ( $this->blogs as $blog ) 333 { 334 $id = $blog->id; 335 336 if ( current_user_can_for_blog( $id, $edit ) && current_user_can_for_blog( $id, $create ) ) 337 { 338 $blogs[ $id ] = $blog->name; 339 } 340 } 341 342 wp_add_inline_script( 343 'supernetwork-post-new', 344 'var blogs = ' . json_encode( $blogs ) . '; var currentId = "' . get_current_blog_id() . '";', 345 'before' 346 ); 347 } 348 } 349 265 350 public function post_types( $args, $field ) 266 351 { … … 276 361 } 277 362 278 public function register_pages() 363 private function push_meta_ids( $meta_ids, $object_id ) 364 { 365 $this->meta_ids[] = array( $meta_ids, $object_id ); 366 } 367 368 public function meta_ids() 369 { 370 return empty( $this->meta_ids ) ? array() : $this->meta_ids[ count( $this->meta_ids ) - 1 ][0]; 371 } 372 373 public function meta_object_id() 374 { 375 return empty( $this->meta_ids ) ? 0 : $this->meta_ids[ count( $this->meta_ids ) - 1 ][1]; 376 } 377 378 public function pop_meta_ids() 379 { 380 array_pop( $this->meta_ids ); 381 } 382 383 public function delete_meta( $meta_ids, $object_id ) 384 { 385 $this->push_meta_ids( $meta_ids, $object_id ); 386 return $meta_ids; 387 } 388 389 public function register() 279 390 { 280 391 $this->page->register(); 392 393 add_filter( 'post_type_link', array( $this, 'intercept_permalink_for_post' ), 10, 2 ); 394 add_filter( 'post_link', array( $this, 'intercept_permalink_for_post' ), 10, 2 ); 395 add_filter( 'page_link', array( $this, 'intercept_permalink' ), 10, 2 ); 396 add_filter( 'preview_post_link', array( $this, 'intercept_preview_link' ), 10, 2 ); 397 add_filter( 'supernetwork_preview_link', array( $this, 'replace_preview_link' ), 10, 2 ); 398 add_filter( 'user_has_cap', array( $this, 'intercept_capability' ), 10, 4 ); 399 add_filter( 'pre_handle_404', array( $this, 'singular_access' ), 10, 2 ); 400 add_action( 'wp', array( $this, 'preview_access' ) ); 401 add_filter( 'query', array( $this, 'intercept_query' ), 10, 2 ); 402 add_filter( 'wp_insert_post', array( $this, 'shared_auto_increment' ), 10, 3 ); 403 add_filter( 'wp_insert_comment', array( $this, 'auto_increment_comments' ), 10, 2 ); 404 add_filter( 'created_term', array( $this, 'auto_increment_terms' ), 10, 4 ); 405 add_filter( 'added_term_relationship', array( $this, 'auto_increment_term_relationships' ), 10, 3 ); 406 add_filter( 'admin_enqueue_scripts', array( $this, 'add_new_post' ) ); 407 add_filter( 'admin_footer', array( $this, 'report_collisions' ) ); 408 add_filter( 'delete_comment_meta', array( $this, 'delete_meta' ), 10, 2 ); 409 add_filter( 'delete_post_meta', array( $this, 'delete_meta' ), 10, 2 ); 410 add_filter( 'delete_term_meta', array( $this, 'delete_meta' ), 10, 2 ); 411 412 // Comments must be queried before posts so as not to mask any comment ID collisions. 413 // Taxonomy terms must be queried before terms so as not to mask any taxonomy term ID collisions. 414 foreach ( array_keys( $this->collisions ) as $entity ) 415 { 416 // `ID` comes before `post_parent` in the `posts` sub-array, so `ID` will be correctly returned. 417 $id = array_search( $entity, WP_Super_Network::TABLES_TO_REPLACE[ $entity ], true ); 418 419 $this->collisions[ $entity ] = $GLOBALS['wpdb']->get_col( 'SELECT `' . $id . '` FROM `' . $GLOBALS['wpdb']->__get( $entity ) . '` GROUP BY `' . $id . '` HAVING COUNT(*) > 1 ORDER BY `' . $id . '` ASC' ); 420 } 421 422 $consolidated = !empty( get_option( 'supernetwork_consolidated', array() )['consolidated'] ); 423 424 if ( $consolidated ) 425 { 426 $this->post_types = array_keys( get_option( 'supernetwork_post_types', array() ) ); 427 } 428 else 429 { 430 $extra_where = ''; 431 $extra_where_2 = ''; 432 $relationships = $GLOBALS['wpdb']->term_relationships; 433 $taxonomy = $GLOBALS['wpdb']->term_taxonomy; 434 435 // The `AND 1=1` is to avoid a bug in the SQL parser caused by consecutive brackets. 436 empty( $this->collisions['comments'] ) or $extra_where .= ' AND `post_id` NOT IN (SELECT `comment_post_ID` FROM `' . $GLOBALS['wpdb']->comments . '` WHERE `comment_ID` IN (' . implode( ', ', $this->collisions[ 'comments' ] ) . ') AND 1=1)'; 437 438 if ( !empty( $this->collisions['term_taxonomy'] ) ) 439 { 440 $extra_where .= ' AND `post_id` NOT IN (SELECT `object_id` FROM `' . $relationships . '` WHERE `term_taxonomy_id` IN (' . implode( ', ', $this->collisions[ 'term_taxonomy' ] ) . ') AND 1=1)'; 441 $extra_where_2 = ' AND `' . $relationships . '`.`term_taxonomy_id` NOT IN (' . implode( ', ', $this->collisions[ 'term_taxonomy' ] ) . ')'; 442 } 443 444 empty( $this->collisions['terms'] ) or $extra_where .= ' AND `post_id` NOT IN (SELECT `object_id` FROM `' . $relationships . '` LEFT JOIN `' . $taxonomy . '` ON `' . $relationships . '`.`term_taxonomy_id` = `' . $taxonomy . '`.`term_taxonomy_id` WHERE `term_id` IN (' . implode( ', ', $this->collisions[ 'terms' ] ) . ')' . $extra_where_2 . ' AND 1=1)'; 445 446 // This query requires post collisions, but not the other collisions, to be recorded. 447 $old_comment_collisions = $this->collisions['comments']; 448 $old_taxonomy_term_collisions = $this->collisions['term_taxonomy']; 449 $old_term_collisions = $this->collisions['terms']; 450 451 foreach ( array( 'comments', 'term_taxonomy', 'terms' ) as $entity ) $this->collisions[ $entity ] = array(); 452 453 $this->republished = $GLOBALS['wpdb']->get_col( 'SELECT DISTINCT `post_id` FROM `' . $GLOBALS['wpdb']->postmeta . '` WHERE `meta_key` = \'_supernetwork_share\'' . $extra_where . ' ORDER BY `post_id` DESC' ); 454 455 $this->collisions['comments'] = $old_comment_collisions; 456 $this->collisions['term_taxonomy'] = $old_taxonomy_term_collisions; 457 $this->collisions['terms'] = $old_term_collisions; 458 } 459 460 $this->consolidated = $consolidated; 461 462 if ( !$this->consolidated && !empty( $this->republished ) ) 463 { 464 $this->set_auto_increment( $this->republished[0] + 1 ); 465 } 466 } 467 468 /** 469 * Selects an entity type to be first in line for collision elimination, based on a calculated score. 470 * 471 * The score for each entity type is the median ID of its collisions, divided by the median ID overall. 472 * A score of 1 means the collisions are evenly distributed. 473 * A lower score means the collisions are concentrated on the lower ID's, which are more likely to be older entities with more references and importance. 474 * 475 * The entity type with the lowest score is selected for collision elimination. 476 * 477 * @since 1.2.0 478 */ 479 private function get_entity_by_median_score() 480 { 481 // Temporarily empty collisions. 482 $old = $this->collisions; 483 $this->collisions = WP_Super_Network::ENTITIES_TO_REPLACE; 484 $scores = WP_Super_Network::ENTITIES_TO_REPLACE; 485 486 foreach ( $scores as $entity => &$score ) 487 { 488 // `ID` comes before `post_parent` in the `posts` sub-array, so `ID` will be correctly returned. 489 $id = array_search( $entity, WP_Super_Network::TABLES_TO_REPLACE[ $entity ], true ); 490 491 $collisions = $GLOBALS['wpdb']->get_col( 'SELECT `' . $id . '` FROM `' . $GLOBALS['wpdb']->__get( $entity ) . '` WHERE `' . $id . '` IN (SELECT `' . $id . '` FROM `' . $GLOBALS['wpdb']->__get( $entity ) . '` GROUP BY `' . $id . '` HAVING COUNT(*) > 1) ORDER BY `' . $id . '` ASC' ); 492 $all = $GLOBALS['wpdb']->get_col( 'SELECT `' . $id . '` FROM `' . $GLOBALS['wpdb']->__get( $entity ) . '` ORDER BY `' . $id . '` ASC' ); 493 494 $score = empty( $collisions ) ? PHP_FLOAT_MAX : $collisions[ floor( count( $collisions ) / 2 ) ] / $all[ floor( count( $all ) / 2 ) ]; 495 } 496 497 $this->collisions = $old; 498 return array_search( min( $scores ), $scores ); 499 } 500 501 /** 502 * Force deletes a term from a taxonomy. 503 * 504 * @since 1.2.0 505 */ 506 private function force_delete_taxonomy_term( $term_id, $taxonomy ) 507 { 508 // Temporarily register taxonomy to force deletion. 509 if ( !( $exists = taxonomy_exists( $taxonomy ) ) ) register_taxonomy( $taxonomy, array() ); 510 511 // 0 is returned if the default term was not deleted. 512 $success = 0 !== wp_delete_term( $term_id, $taxonomy ); 513 514 if ( !$exists ) unregister_taxonomy( $taxonomy ); 515 return $success; 281 516 } 282 517 … … 288 523 if ( $this->consolidated ) 289 524 { 290 echo '<h2>Post ID Collisions</h2>'; 291 292 if ( !empty( $this->collisions ) && isset( $_POST['supernetwork_post_collision_' . $this->collisions[0] ] ) ) 293 { 294 $this->consolidated = false; 295 296 foreach ( $this->blogs as $blog ) 525 $entity = $this->get_entity_by_median_score(); 526 527 $this->consolidated = false; 528 529 echo '<h2>ID Collisions</h2>'; 530 531 if ( !empty( $this->collisions[ $entity ] ) && isset( $_POST[ 'supernetwork_' . $entity . '_collision_' . $this->collisions[ $entity ][0] ] ) ) 532 { 533 $success = true; 534 535 foreach ( $this->blogs as $blog ) 536 { 537 if ( $blog->id !== (int) explode( '_', $_POST[ 'supernetwork_' . $entity . '_collision_' . $this->collisions[ $entity ][0] ] )[0] ) 297 538 { 298 539 switch_to_blog( $blog->id ); 299 300 if ( empty( $GLOBALS['wpdb']->get_col( 'select ID from ' . $blog->table( 'posts' ) . ' where guid = \'' . $_POST['supernetwork_post_collision_' . $this->collisions[0] ] . '\' limit 1' ) ) ) 540 541 if ( $entity === 'comments' ) wp_delete_comment( (int) $this->collisions['comments'][0], true ); 542 if ( $entity === 'posts' ) wp_delete_post( (int) $this->collisions['posts'][0], true ); 543 544 if ( $entity === 'term_taxonomy' ) 301 545 { 302 wp_delete_post( (int) $this->collisions[0], true ); 546 $term = $GLOBALS['wpdb']->get_row( 'SELECT `taxonomy`, `term_id` FROM `' . $GLOBALS['wpdb']->term_taxonomy . '` WHERE `term_taxonomy_id` = ' . $this->collisions['term_taxonomy'][0], ARRAY_A ); 547 548 if ( isset( $term['term_id'] ) && isset( $term['taxonomy'] ) ) 549 { 550 $success = $this->force_delete_taxonomy_term( (int) $term['term_id'], $term['taxonomy'] ) && $success; 551 } 552 } 553 554 if ( $entity === 'terms' ) 555 { 556 $results = $GLOBALS['wpdb']->get_results( 'SELECT `taxonomy`, `term_id` FROM `' . $GLOBALS['wpdb']->term_taxonomy . '` WHERE `term_id` = ' . $this->collisions['terms'][0] ); 557 558 foreach ( $results as $result ) 559 { 560 $success = $this->force_delete_taxonomy_term( (int) $result['term_id'], $result['taxonomy'] ) && $success; 561 } 303 562 } 304 563 305 564 restore_current_blog(); 306 565 } 307 566 } 567 568 // Start again if collision was eliminated. 569 if ( $success ) 570 { 571 array_shift( $this->collisions[ $entity ] ); 308 572 $this->consolidated = true; 309 array_shift( $this->collisions ); 310 } 311 312 if ( empty( $this->collisions ) ) 313 { 314 echo '<p>No collisions on this network!</p>'; 315 } 316 else 317 { 318 $old_collisions = $this->collisions; 319 $old_post_types = $this->post_types; 320 321 $this->collisions = array(); 322 $this->post_types = array(); 323 324 echo '<p>Consolidated mode is designed for fresh networks. When activated on an existing network, a large number of ID collisions are inevitable. However, you may be able to eliminate some collisions when the ID refers to a post of low importance, such as a revision or autosave.</p>'; 325 echo '<p>The below table allows you to eliminate post ID collisions, one ID at a time. For each ID, you must select just ONE post to keep. All others with the same ID will be immediately and irretrievably deleted.</p>'; 326 327 echo '<form method="post" action="">'; 328 echo '<table class="widefat">'; 329 echo '<thead><tr><th scope="col">Keep?</th><th scope="col">GUID</th><th scope="col">Post Title</th><th scope="col">Post Preview</th><th scope="col">Post Type</th><th scope="col">Post Status</th></tr></thead>'; 330 echo '<tbody>'; 331 332 foreach ( $GLOBALS['wpdb']->get_results( 'select guid, post_title, substring(post_content, 1, 500) as post_preview, post_type, post_status from ' . $GLOBALS['wpdb']->posts . ' where ID = ' . $old_collisions[0], ARRAY_A ) as $site ) 573 $entity = $this->get_entity_by_median_score(); 574 $this->consolidated = false; 575 } 576 } 577 578 if ( empty( $this->collisions[ $entity ] ) ) 579 { 580 echo '<p>No collisions on this network!</p>'; 581 } 582 else 583 { 584 // `ID` comes before `post_parent` in the `posts` sub-array, so `ID` will be correctly returned. 585 $id = array_search( $entity, WP_Super_Network::TABLES_TO_REPLACE[ $entity ], true ); 586 $id = 'terms' === $entity ? 't.' . $id : $id; 587 588 $fields = array( 589 'comments' => array( 590 'post_title', 591 'comment_content', 592 'comment_author', 593 'comment_author_email' 594 ), 595 'posts' => array( 596 'post_title', 597 'post_content', 598 'post_type', 599 'post_status' 600 ), 601 'term_taxonomy' => array( 602 'name', 603 'description', 604 'taxonomy', 605 'count' 606 ), 607 'terms' => array( 608 'name', 609 'description', 610 'taxonomy', 611 'count' 612 ) 613 ); 614 615 $labels = array( 616 'comments' => array( 617 'Site Containing Post/Comment', 618 'Post Containing Comment', 619 'Comment Preview', 620 'Comment Author', 621 'Comment Author Email' 622 ), 623 'posts' => array( 624 'Site Containing Post', 625 'Post Title', 626 'Post Preview', 627 'Post Type', 628 'Post Status' 629 ), 630 'term_taxonomy' => array( 631 'Site Containing Taxonomy Term', 632 'Taxonomy Term Name', 633 'Taxonomy Term Description', 634 'Taxonomy', 635 'Post Count' 636 ), 637 'terms' => array( 638 'Site Containing Taxonomy Term', 639 'Taxonomy Term Name', 640 'Taxonomy Term Description', 641 'Taxonomy', 642 'Post Count' 643 ) 644 ); 645 646 echo '<p>Consolidated mode is designed for fresh networks. When activated on an existing network, a large number of ID collisions are inevitable. However, you may be able to eliminate some collisions when the ID refers to a post of low importance, such as a revision or autosave.</p>'; 647 echo '<p>The below tables allow you to eliminate post, taxonomy term and comment ID collisions, one ID at a time. For each ID, you must select just ONE entity to keep. All others with the same ID will be immediately and irretrievably deleted.</p>'; 648 649 if ( in_array( $entity, array( 'term_taxonomy', 'terms' ), true ) ) echo '<div class="notice notice-warning inline"><p><strong>Note:</strong> Default taxonomy terms can not be deleted! If a default taxonomy term is in a collision, you must go to Writing Settings and select a new default term for the taxonomy before you can eliminate the collision.</p></div>'; 650 651 echo '<form method="post" action="">'; 652 echo '<table class="widefat">'; 653 echo '<thead><tr><th scope="col">Keep?</th><th scope="col">' . $labels[ $entity ][0] . '</th><th scope="col">' . $labels[ $entity ][1] . '</th><th scope="col">' . $labels[ $entity ][2] . '</th><th scope="col">' . $labels[ $entity ][3] . '</th><th scope="col">' . $labels[ $entity ][4] . '</th></tr></thead>'; 654 echo '<tbody>'; 655 656 foreach ( $this->blogs as $blog ) 657 { 658 $tables = array( 659 'comments' => $blog->table( 'comments' ) . ' LEFT JOIN ' . $blog->table( 'posts' ) . ' ON comment_post_ID = ID', 660 'posts' => $blog->table( 'posts' ), 661 'term_taxonomy' => $blog->table( 'term_taxonomy' ) . ' tt LEFT JOIN ' . $blog->table( 'terms' ) . ' t ON tt.term_id = t.term_id', 662 'terms' => $blog->table( 'term_taxonomy' ) . ' tt LEFT JOIN ' . $blog->table( 'terms' ) . ' t ON tt.term_id = t.term_id' 663 ); 664 665 $rows = $GLOBALS['wpdb']->get_results( 'SELECT `' . $fields[ $entity ][0] . '`, SUBSTRING(`' . $fields[ $entity ][1] . '`, 1, 500) AS `' . $fields[ $entity ][1] . '_preview`, `' . $fields[ $entity ][2] . '`, `' . $fields[ $entity ][3] . '` FROM ' . $tables[ $entity ] . ' WHERE `' . $id . '` = ' . $this->collisions[ $entity ][0], ARRAY_A ); 666 667 // There may be more than one row if terms are not split. 668 foreach ( $rows as $key => $row ) 333 669 { 334 670 echo '<tr>'; 335 echo '<td><input type="radio" id="supernetwork__' . esc_ textarea( $site['guid'] ) . '" value="' . esc_textarea( $site['guid'] ) . '" name="supernetwork_post_collision_' . $old_collisions[0] . '"></td>';336 echo '<td><label for="supernetwork__' . esc_ textarea( $site['guid'] ) . '">' . esc_textarea( $site['guid']) . '</label></td>';337 echo '<td><label for="supernetwork__' . esc_ textarea( $site['guid'] ) . '">' . esc_textarea( $site['post_title'] ) . '</label></td>';338 echo '<td><label for="supernetwork__' . esc_ textarea( $site['guid'] ) . '">' . esc_textarea( $site['post_preview'] ) . '</label></td>';339 echo '<td><label for="supernetwork__' . esc_ textarea( $site['guid'] ) . '">' . esc_textarea( $site['post_type'] ) . '</label></td>';340 echo '<td><label for="supernetwork__' . esc_ textarea( $site['guid'] ) . '">' . esc_textarea( $site['post_status'] ) . '</label></td>';671 echo '<td><input type="radio" id="supernetwork__' . esc_attr( $blog->id ) . '_' . $key . '" value="' . esc_attr( $blog->id ) . '" name="supernetwork_' . $entity . '_collision_' . $this->collisions[ $entity ][0] . '"></td>'; 672 echo '<td><label for="supernetwork__' . esc_attr( $blog->id ) . '_' . $key . '">' . esc_html( $blog->name ) . '</label></td>'; 673 echo '<td><label for="supernetwork__' . esc_attr( $blog->id ) . '_' . $key . '">' . esc_html( $row[ $fields[ $entity ][0] ] ) . '</label></td>'; 674 echo '<td><label for="supernetwork__' . esc_attr( $blog->id ) . '_' . $key . '">' . esc_html( $row[ $fields[ $entity ][1] . '_preview' ] ) . '</label></td>'; 675 echo '<td><label for="supernetwork__' . esc_attr( $blog->id ) . '_' . $key . '">' . esc_html( $row[ $fields[ $entity ][2] ] ) . '</label></td>'; 676 echo '<td><label for="supernetwork__' . esc_attr( $blog->id ) . '_' . $key . '">' . esc_html( $row[ $fields[ $entity ][3] ] ) . '</label></td>'; 341 677 echo '</tr>'; 342 678 } 343 344 $this->collisions = $old_collisions; 345 $this->post_types = $old_post_types;346 347 echo '</tbody></table>';348 submit_button( 'Keep Selected and Delete All Others' );349 echo '</form>'; 350 }351 }679 } 680 681 echo '</tbody></table>'; 682 submit_button( 'Keep Selected and Delete All Others' ); 683 echo '</form>'; 684 } 685 686 $this->consolidated = true; 687 } 352 688 else 353 689 { … … 419 755 public function intercept_query( $query ) 420 756 { 421 try 422 { 423 $parsed = $this->parser->parse( $query ); 424 425 // Insert, update and delete queries are not currently modified. 426 return $this->modify_query( $parsed ) ? $this->creator->create( $parsed ) : $query; 427 } 428 catch ( \PHPSQLParser\exceptions\UnsupportedFeatureException $uf ) 429 { 430 return $query; 431 } 432 catch ( \PHPSQLParser\exceptions\UnableToCreateSQLException $utcsql ) 433 { 434 return $query; 435 } 436 } 437 438 private function modify_query( &$parsed ) 439 { 440 if ( in_array( array_keys( $parsed )[0], array( 'UNION', 'SELECT' ), true ) ) 441 { 442 if ( isset( $parsed['UNION'] ) ) 443 { 444 foreach ( $parsed['UNION'] as &$query ) 445 { 446 $this->modify_query( $query ); 447 } 448 } 449 else 450 { 451 if ( isset( $parsed['SELECT'] ) && isset( $parsed['FROM'] ) ) 452 { 453 foreach ( $parsed['FROM'] as &$from ) 454 { 455 if ( !empty( $from['sub_tree'] ) ) 456 { 457 $this->modify_query( $from['sub_tree'] ); 458 } 459 else 460 { 461 foreach ( array_keys( self::ID_COLS ) as $table ) 462 { 463 $local_table = $GLOBALS['wpdb']->__get( $table ); 464 465 if ( isset( $from['table'] ) && $from['table'] === $local_table ) 466 { 467 if ( ( $union = $this->union( $table ) ) === $from['table'] ) 468 { 469 continue; 470 } 471 472 $from['expr_type'] = 'subquery'; 473 $from['sub_tree'] = $this->parser->parse( $union ); 474 475 if ( false === $from['alias'] ) 476 { 477 $from['alias'] = array( 478 'as' => false, 479 'name' => $local_table, 480 'no_quotes' => array( 481 'delim' => false, 482 'parts' => array( $local_table ) 483 ), 484 'base_expr' => $local_table 485 ); 486 } 487 488 unset( $from['table'] ); 489 break; 490 } 491 } 492 } 493 } 494 495 foreach ( array( 'WHERE', 'HAVING' ) as $clause ) 496 { 497 if ( isset( $parsed[ $clause ] ) ) 498 { 499 foreach ( $parsed[ $clause ] as &$subclause ) 500 { 501 if ( !empty( $subclause['sub_tree'] ) ) 502 { 503 $this->modify_query( $subclause['sub_tree'] ); 504 } 505 } 506 } 507 } 508 } 509 } 510 511 return true; 512 } 513 514 return false; 757 $cache = wp_cache_get( 'supernetwork_queries', $query ); 758 759 if ( $cache ) 760 { 761 return $cache->transformed; 762 } 763 else 764 { 765 $transformed = new Query( $query, $this ); 766 wp_cache_set( 'supernetwork_queries', $transformed, $query ); 767 return $transformed->transformed; 768 } 515 769 } 516 770 517 771 public function intercept_permalink( $permalink, $post_ID ) 518 772 { 519 // Prevent infinite loop 520 if ( empty( $GLOBALS['_wp_switched_stack'] ) ) 521 { 522 if ( !is_null( $blog = $this->get_blog( $post_ID ) ) ) 523 { 524 switch_to_blog( $blog->id ); 525 $permalink = get_permalink( $post_ID ); 526 restore_current_blog(); 527 } 773 if ( !doing_filter( 'supernetwork_preview_link' ) && !is_null( $blog = $this->get_blog( $post_ID ) ) ) 774 { 775 switch_to_blog( $blog->id ); 776 $permalink = get_permalink( $post_ID ); 777 restore_current_blog(); 528 778 } 529 779 … … 536 786 } 537 787 538 public function intercept_capability_write( $caps, $cap, $user_id, $args ) 539 { 540 if ( in_array( $cap, array( 'delete_post', 'edit_post', 'publish_post' ), true ) ) 541 { 542 if ( !is_null( $blog = $this->get_blog( get_post( $args[0] )->ID ) ) && get_current_blog_id() !== $blog->id ) 543 { 544 return array( 'do_not_allow' ); 545 } 546 } 547 548 return $caps; 549 } 550 551 public function intercept_capability_read( $allcaps, $caps, $args, $user ) 552 { 553 if ( $args[0] === 'read_post' ) 788 public function intercept_preview_link( $preview_link, $post ) 789 { 790 return doing_filter( 'supernetwork_preview_link' ) ? $preview_link : apply_filters( 'supernetwork_preview_link', $preview_link, $post ); 791 } 792 793 public function replace_preview_link( $preview_link, $post ) 794 { 795 $query_args = array(); 796 parse_str( parse_url( $preview_link, PHP_URL_QUERY ), $query_args ); 797 return get_preview_post_link( $post, array_intersect_key( $query_args, array( 'preview_nonce' => null, 'preview_id' => null ) ) ); 798 } 799 800 public function intercept_capability( $allcaps, $caps, $args, $user ) 801 { 802 if ( in_array( $args[0], array( 'delete_post', 'edit_post', 'publish_post', 'read_post' ), true ) ) 554 803 { 555 804 if ( !is_null( $blog = $this->get_blog( get_post( $args[2] )->ID ) ) ) … … 564 813 public function singular_access( $false, $wp_query ) 565 814 { 566 if ( $wp_query->is_singular() && !is_admin() && ( !defined( 'REST_REQUEST' ) || !REST_REQUEST ) && isset( $wp_query->post ) && !is_null( $blog = $this->get_blog( $wp_query->post->ID ) ) && get_current_blog_id() !== $blog->id)815 if ( $wp_query->is_singular() && !is_admin() && ( !defined( 'REST_REQUEST' ) || !REST_REQUEST ) && !is_preview() && isset( $wp_query->post ) && !is_null( $this->get_blog( $wp_query->post->ID ) ) ) 567 816 { 568 817 $wp_query->set_404(); … … 575 824 } 576 825 577 private function get_blog( $id ) 578 { 579 if ( !in_array( (string) $id, $this->collisions, true ) ) 580 { 826 public function preview_access() 827 { 828 if ( is_preview() && !is_null( $blog = $this->get_blog( get_the_ID() ) ) ) 829 { 830 switch_to_blog( $blog->id ); 831 } 832 } 833 834 public function get_blog( $id, $entity = 'posts' ) 835 { 836 if ( in_array( $entity, array_keys( WP_Super_Network::ENTITIES_TO_REPLACE ), true ) && !in_array( (string) $id, $this->collisions[ $entity ], true ) && ( $this->consolidated || !empty( $this->republished ) && $entity !== 'posts' || in_array( (string) $id, $this->republished, true ) ) ) 837 { 838 if ( isset( $this->blog_cache[ $entity ][ $id ] ) ) return get_current_blog_id() === $this->blog_cache[ $entity ][ $id ]->id ? null : $this->blog_cache[ $entity ][ $id ]; 839 840 $republished = null; 841 $extra_where = ''; 842 843 // `ID` comes before `post_parent` in the `posts` sub-array, so `ID` will be correctly returned. 844 $col = array_search( $entity, WP_Super_Network::TABLES_TO_REPLACE[ $entity ], true ); 845 $select = $entity === 'posts' ? 'post_type' : $col; 846 581 847 $old_consolidated = $this->consolidated; 582 848 $old_republished = $this->republished; … … 587 853 foreach ( $this->blogs as $blog ) 588 854 { 589 if ( !empty( $GLOBALS['wpdb']->get_col( 'select ID from ' . $blog->table( 'posts' ) . ' where ID = ' . $id . ' limit 1' ) ) ) 855 if ( !$old_consolidated ) 856 { 857 isset( $republished ) or $republished = '(' . implode( ', ', $old_republished ) . ')'; 858 859 // The `AND 1=1` is to avoid a bug in the SQL parser caused by consecutive brackets. 860 $entity !== 'comments' or $extra_where = ' AND `comment_post_ID` IN ' . $republished . ' AND 1=1'; 861 $entity !== 'term_taxonomy' or $extra_where = ' AND EXISTS (SELECT * FROM `' . $blog->table( 'term_relationships' ) . '` WHERE `term_taxonomy_id` = ' . $id . ' AND `object_id` IN ' . $republished . ' AND 1=1)'; 862 $entity !== 'terms' or $extra_where = ' AND EXISTS (SELECT * FROM `' . $blog->table( 'term_relationships' ) . '` WHERE `term_taxonomy_id` IN (SELECT `term_taxonomy_id` FROM ' . $blog->table( 'term_taxonomy' ) . ' WHERE `term_id` = ' . $id . ') AND `object_id` IN ' . $republished . ' AND 1=1)'; 863 } 864 865 $result = $GLOBALS['wpdb']->get_var( 'SELECT `' . $select . '` FROM `' . $blog->table( $entity ) . '` WHERE `' . $col . '` = ' . $id . $extra_where . ' LIMIT 1' ); 866 867 if ( !empty( $result ) ) 590 868 { 591 869 $this->consolidated = $old_consolidated; 592 870 $this->republished = $old_republished; 593 871 594 return $blog; 872 $this->blog_cache[ $entity ][ $id ] = $this->consolidated && in_array( $result, $this->post_types, true ) && !$blog->is_network() ? null : $blog; 873 return get_current_blog_id() === $blog->id ? null : $this->blog_cache[ $entity ][ $id ]; 595 874 } 596 875 } … … 600 879 } 601 880 602 return null; 881 return $this->blog_cache[ $entity ][ $id ] = null; 882 } 883 884 public function get_blog_by_id( $id ) 885 { 886 return isset( $this->blogs[ $id ] ) ? $this->blogs[ $id ] : null; 603 887 } 604 888 } -
wp-super-network/tags/1.2.0/src/plugin.php
r2671848 r2864100 7 7 class WP_Super_Network 8 8 { 9 const ENTITIES_TO_REPLACE = array( 10 'comments' => array(), 11 'posts' => array(), 12 'term_taxonomy' => array(), 13 'terms' => array() 14 ); 15 16 const TABLES_TO_REPLACE = array( 17 'commentmeta' => array( 18 'comment_id' => 'comments' 19 ), 20 'comments' => array( 21 'comment_ID' => 'comments', 22 'comment_post_ID' => 'posts' 23 ), 24 'postmeta' => array( 25 'post_id' => 'posts' 26 ), 27 'posts' => array( 28 'ID' => 'posts', 29 'post_parent' => 'posts' 30 ), 31 'term_relationships' => array( 32 'object_id' => 'posts', 33 'term_taxonomy_id' => 'term_taxonomy' 34 ), 35 'term_taxonomy' => array( 36 'term_id' => 'terms', 37 'term_taxonomy_id' => 'term_taxonomy' 38 ), 39 'termmeta' => array( 40 'term_id' => 'terms' 41 ), 42 'terms' => array( 43 'term_id' => 'terms' 44 ) 45 ); 46 9 47 /** 10 48 * Static instance of the plugin. … … 45 83 } 46 84 47 function options( $value, $option)85 public static function options( $value, $option, $default ) 48 86 { 49 87 if ( !is_main_site() ) 50 88 { 51 switch_to_blog( get_main_site_id() ); 52 $value = get_option( $option ); 53 restore_current_blog(); 89 $main = get_main_site_id(); 90 91 if ( $main > 0 ) 92 { 93 switch_to_blog( $main ); 94 $value = get_option( $option, $default ); 95 restore_current_blog(); 96 } 97 else 98 { 99 if ( in_array( $option, array( 'supernetwork_consolidated', 'supernetwork_post_types', 'supernetwork_options' ), true ) ) 100 { 101 $value = $default; 102 } 103 } 104 105 if ( $value === false ) 106 { 107 add_filter( 'option_' . $option, '__return_false' ); 108 } 54 109 } 55 110 … … 57 112 } 58 113 114 public static function add_option( $option, $value ) 115 { 116 if ( !is_main_site() && has_filter( 'pre_option_' . $option, array( __CLASS__, 'options' ) ) ) 117 { 118 $main = get_main_site_id(); 119 120 if ( $main > 0 && current_user_can_for_blog( $main, 'manage_options' ) ) 121 { 122 switch_to_blog( $main ); 123 add_option( $option, $value ); 124 restore_current_blog(); 125 } 126 } 127 } 128 129 public static function update_option( $option, $old_value, $value ) 130 { 131 if ( !is_main_site() && has_filter( 'pre_option_' . $option, array( __CLASS__, 'options' ) ) ) 132 { 133 $main = get_main_site_id(); 134 135 if ( $main > 0 && current_user_can_for_blog( $main, 'manage_options' ) ) 136 { 137 switch_to_blog( $main ); 138 update_option( $option, $value ); 139 restore_current_blog(); 140 } 141 } 142 } 143 59 144 /** 60 145 * Launch the initialization process. … … 64 149 public function run() 65 150 { 151 // Complete this before accessing the option on next line 152 add_filter( 'pre_option_supernetwork_options', array( __CLASS__, 'options' ), 10, 3 ); 153 add_filter( 'pre_option_supernetwork_post_types', array( __CLASS__, 'options' ), 10, 3 ); 154 add_filter( 'pre_option_supernetwork_consolidated', array( __CLASS__, 'options' ), 10, 3 ); 155 156 foreach ( get_option( 'supernetwork_options', array() ) as $option => $val ) 157 { 158 if ( $val && strpos( $option, '_' ) !== 0 && strpos( $option, 'supernetwork_' ) !== 0 ) 159 { 160 add_filter( 'pre_option_' . $option, array( __CLASS__, 'options' ), 10, 3 ); 161 } 162 } 163 164 add_filter( 'add_option', array( __CLASS__, 'add_option' ), 10, 2 ); 165 add_filter( 'update_option', array( __CLASS__, 'update_option' ), 10, 3 ); 166 167 // Disable querying of meta ID. See issue #10 168 add_filter( 'update_comment_metadata_by_mid', '__return_false' ); 169 add_filter( 'update_post_metadata_by_mid', '__return_false' ); 170 add_filter( 'update_term_metadata_by_mid', '__return_false' ); 171 add_filter( 'delete_comment_metadata_by_mid', '__return_false' ); 172 add_filter( 'delete_post_metadata_by_mid', '__return_false' ); 173 add_filter( 'delete_term_metadata_by_mid', '__return_false' ); 174 175 $this->network->register(); 176 66 177 // Load functions 67 add_filter( 'post_type_link', array( $this->network, 'intercept_permalink_for_post' ), 10, 2 );68 add_filter( 'post_link', array( $this->network, 'intercept_permalink_for_post' ), 10, 2 );69 add_filter( 'page_link', array( $this->network, 'intercept_permalink' ), 10, 2 );70 add_filter( 'map_meta_cap', array( $this->network, 'intercept_capability_write' ), 10, 4 );71 add_filter( 'user_has_cap', array( $this->network, 'intercept_capability_read' ), 10, 4 );72 add_filter( 'pre_handle_404', array( $this->network, 'singular_access' ), 10, 2 );73 add_filter( 'query', array( $this->network, 'intercept_query' ), 10, 2 );74 add_filter( 'wp_insert_post', array( $this->network, 'shared_auto_increment' ), 10, 3 );75 178 add_filter( 'network_admin_menu', array( $this, 'summary' ) ); 76 77 if ( is_main_site() ) $this->network->register_pages();78 79 // Complete this before accessing the option on next line80 add_filter( 'pre_option_supernetwork_options', array( $this, 'options' ), 10, 2 );81 add_filter( 'pre_option_supernetwork_post_types', array( $this, 'options' ), 10, 2 );82 add_filter( 'pre_option_supernetwork_consolidated', array( $this, 'options' ), 10, 2 );83 84 foreach ( (array) get_option( 'supernetwork_options' ) as $option => $val )85 {86 if ( $val && strpos( $option, '_' ) !== 0 && strpos( $option, 'supernetwork_' ) !== 0 )87 {88 add_filter( 'pre_option_' . $option, array( $this, 'options' ), 10, 2 );89 }90 }91 92 $this->network->consolidated = !empty( get_option( 'supernetwork_consolidated' )['consolidated'] );93 179 94 180 if ( !$this->network->consolidated ) … … 154 240 if ( current_user_can( 'edit_post', $post->ID ) ) 155 241 { 156 if ( in_array( (string) $post->ID, $this->network->collisions, true ) ) 242 $collisions = $this->network->collisions; 243 244 if ( array_intersect( get_comments( 'fields=ids&post_id=' . $post->ID ), $collisions['comments'] ) !== array() || in_array( (string) $post->ID, $collisions['posts'], true ) || array_intersect( wp_get_object_terms( $post->ID, array_keys( $GLOBALS['wp_taxonomies'] ), 'fields=tt_ids' ), $collisions['term_taxonomy'] ) !== array() || array_intersect( wp_get_object_terms( $post->ID, array_keys( $GLOBALS['wp_taxonomies'] ), 'fields=ids' ), $collisions['terms'] ) !== array() ) 157 245 { 158 246 $actions['republish'] = '<i style="color: #888;">' . __( 'Can't Republish', 'supernetwork' ) . '</i>'; -
wp-super-network/tags/1.2.0/wp-super-network.php
r2671848 r2864100 4 4 * Plugin URI: 5 5 * Description: Share content between sites and create offspring networks. 6 * Version: 1. 1.06 * Version: 1.2.0 7 7 * Requires at least: 5.0 8 * Requires PHP: 5.68 * Requires PHP: 7.2 9 9 * Author: Ask Carlo 10 10 * Author URI: https://askcarlo.com -
wp-super-network/trunk/init.php
r2671848 r2864100 24 24 require_once SUPER_NETWORK_DIR . 'vendor/autoload.php'; 25 25 26 // Load superclasses. 27 require_once SUPER_NETWORK_DIR . 'src/node.php'; 28 26 29 // Load classes. 27 30 require_once SUPER_NETWORK_DIR . 'src/blog.php'; 31 require_once SUPER_NETWORK_DIR . 'src/expression.php'; 28 32 require_once SUPER_NETWORK_DIR . 'src/field.php'; 33 require_once SUPER_NETWORK_DIR . 'src/insert.php'; 29 34 require_once SUPER_NETWORK_DIR . 'src/network.php'; 30 35 require_once SUPER_NETWORK_DIR . 'src/page.php'; 31 36 require_once SUPER_NETWORK_DIR . 'src/plugin.php'; 37 require_once SUPER_NETWORK_DIR . 'src/query.php'; 32 38 require_once SUPER_NETWORK_DIR . 'src/section.php'; 39 require_once SUPER_NETWORK_DIR . 'src/subquery.php'; 40 require_once SUPER_NETWORK_DIR . 'src/table.php'; 33 41 34 42 // Initialize the plugin. -
wp-super-network/trunk/readme.txt
r2671848 r2864100 2 2 Contributors: manfcarlo 3 3 Tags: network, multisite, share, sharing, move, migrate, migration, duplicate, syndication, content, management 4 Tested up to: 5.95 Stable tag: 1. 1.04 Tested up to: 6.1 5 Stable tag: 1.2.0 6 6 License: GPLv2 or later 7 7 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 40 40 = How do I republish a page to the whole network? = 41 41 42 As of version 1.0.5, you could flag any post or page on a main site for republication by finding it on the Posts or Pages screen and clicking Republish, but it would not take any effect. Version 1.1.0 now implements the behaviour of this feature! 42 Flag any post or page on a main site for republication by finding it on the Posts or Pages screen and clicking Republish. 43 43 44 44 After you have republished the post or page, it will start showing up across every site on your network. You can also access the network admin and click on Super Network. You will see a list of all posts and pages across all sites in the network that have been flagged for republication. … … 52 52 = How do I turn an existing site into the main site for a new network? = 53 53 54 This feature is s till to come.54 This feature is scheduled for the next major release. 55 55 56 56 = How do I manage all of my sites in a single admin area? = … … 62 62 If two or more sites on a network have a post with the same numeric ID, none of these posts is able to be accessed on consolidated mode. You can eliminate post ID collisions by deleting posts you don't want via the network admin screen > Super Network. 63 63 64 = Why are republished posts and pages not editable in the admin area? =64 = Is this the same as Global Terms? = 65 65 66 This feature is still to come. 66 No! 67 68 WP Super Network does not enable any "global" data relationships. The data you can create with WP Super Network is the same as the data you can create without it. WP Super Network merely brings the management and the viewing of the data together into the one place. 69 70 = Is this the same as WP Multi Network? = 71 72 No! 73 74 WP Super Network is compatible with multi-network environments, but it does not include the management tools that WP Multi Network includes. Therefore, the two plugins have different purposes and can work well together. 75 76 = Is this the same as Distributor? = 77 78 No! 79 80 WP Super Network does not duplicate content between sites, and all posts will always continue to have one and only one permalink. Posts from across the network may preview in archives, but their permalinks will always direct back to their original respective sites. 81 82 = Is this the same as MainWP? = 83 84 Only a little bit. 85 86 WP Super Network and MainWP share a few similar features. However, MainWP is only a good fit for users who don't use the block editor and don't use multisite, since MainWP is yet to announce any support for the block editor and MainWP's own documentation states that it does not test or support multisite. This is despite the block editor and multisite both being core WordPress features for a significant number of years. 87 88 Also, MainWP's features do not integrate with the core WordPress interface and can only be accessed through its own specially-built interface. 89 90 Although WP Super Network requires multisite, it does not require you to learn a new interface. Instead, you can continue working with the core WordPress interfaces you are already familiar with, including both the block editor and classic editor. 91 92 = Which plugins are incompatible with WP Super Network? = 93 94 WP Super Network is incompatible with [Link Manager](https://wordpress.org/plugins/link-manager/). Users of Link Manager will not be supported and are advised to not install WP Super Network. 95 96 WP Super Network is currently incompatible with [SQLite Database Integration](https://wordpress.org/plugins/sqlite-database-integration/). Users of SQLite Database Integration are advised to not install WP Super Network for the time being. Compatibility is planned for a later release, so check back later. 97 98 If you use a plugin that adds a db.php drop-in, it may or may not be incompatible with WP Super Network. To know if you have a db.php drop-in, go to the Plugins screen, click on Drop-in and check if db.php is listed. If the db.php drop-in was added by a plugin that follows the WordPress conventions well and has high user ratings, you are unlikely to see any incompatibility issues with WP Super Network, but you are welcome to ask on the support forum. 99 100 = Is WP Super Network safe to install? = 101 102 See above question about incompatible plugins. 103 104 For best results, the free version of WP Super Network should be activated on a fresh network. While results will vary, you may find that some features (e.g. consolidated mode) are limited in utility for existing networks and/or cause a slow-down in performance for large networks. 105 106 None the less, the utmost care has been taken to avoid permanent data loss or corruption. Keeping regular database back-ups is always recommended, but deactivating the plugin should return your network back to its previous state. 107 108 A premium version is planned for the future, focusing on enhanced performance for medium-to-large networks. 67 109 68 110 = How do I suggest a new feature or submit a bug report? = … … 71 113 72 114 == Changelog == 115 116 = 1.2.0 = 117 * Republished posts and pages can be inserted, updated and deleted 118 * Availability of taxonomies and comments for republished posts 119 * Changed behaviour of select queries that target a specific post 120 * Cache implemented for duplicate queries 121 * Fixed a few bugs with network-based options 122 * Minimum PHP version lifted to 7.2 73 123 74 124 = 1.1.0 = -
wp-super-network/trunk/src/blog.php
r2671848 r2864100 38 38 return $this->wp_site->site_id; 39 39 } 40 41 if ( $key === 'name' ) 42 { 43 return $this->wp_site->blogname; 44 } 40 45 } 41 46 -
wp-super-network/trunk/src/network.php
r2671848 r2864100 7 7 class Network 8 8 { 9 const ID_COLS = array(10 'comments' => 'comment_post_ID',11 'postmeta' => 'post_id',12 'posts' => 'ID',13 'term_relationships' => 'object_id'14 );15 16 9 /** 17 10 * Supernetwork … … 54 47 private $page; 55 48 56 private $parser; 57 private $creator; 58 59 private $collisions = array(); 49 /** 50 * Keeps track of ID collisions. 51 * 52 * @since 1.2.0 53 * @var array 54 */ 55 private $collisions = WP_Super_Network::ENTITIES_TO_REPLACE; 56 57 /** 58 * Cache that maps ID's to blogs. 59 * 60 * @since 1.2.0 61 * @var array 62 */ 63 private $blog_cache = WP_Super_Network::ENTITIES_TO_REPLACE; 64 60 65 private $republished = array(); 61 66 private $post_types = array(); 62 67 68 private $meta_ids = array(); 69 63 70 public function shared_auto_increment( $post_ID, $post, $update ) 64 71 { … … 69 76 } 70 77 71 private function set_auto_increment( $new ) 78 public function auto_increment_comments( $id, $comment ) 79 { 80 if ( $this->consolidated || in_array( (string) $comment->comment_post_ID, $this->republished, true ) ) 81 { 82 $this->set_auto_increment( $id + 1, 'comments' ); 83 } 84 } 85 86 public function auto_increment_terms( $term_id, $tt_id, $taxonomy, $args ) 87 { 88 if ( $this->consolidated ) 89 { 90 $this->set_auto_increment( $tt_id + 1, 'term_taxonomy' ); 91 $this->set_auto_increment( $term_id + 1, 'terms' ); 92 } 93 } 94 95 public function auto_increment_term_relationships( $object_id, $tt_id, $taxonomy ) 96 { 97 if ( !$this->consolidated && in_array( (string) $object_id, $this->republished, true ) ) 98 { 99 $this->set_auto_increment( $tt_id + 1, 'term_taxonomy' ); 100 $term = get_term_by( 'term_taxonomy_id', $tt_id ) and $this->set_auto_increment( $term->term_id + 1, 'terms' ); 101 } 102 } 103 104 private function set_auto_increment( $new, $entity = 'posts' ) 72 105 { 73 106 foreach ( $this->blogs as $blog ) 74 107 { 75 if ( $new > (int) $GLOBALS['wpdb']->get_var( ' select auto_increment from information_schema.tables where table_schema = \'' . DB_NAME . '\' and table_name = \'' . $blog->table( 'posts') . '\'' ) )76 { 77 $GLOBALS['wpdb']->query( ' alter table ' . $blog->table( 'posts' ) . ' auto_increment= ' . (string) $new );108 if ( $new > (int) $GLOBALS['wpdb']->get_var( 'SELECT `auto_increment` FROM `information_schema`.`tables` WHERE `table_schema` = \'' . DB_NAME . '\' AND `table_name` = \'' . $blog->table( $entity ) . '\'' ) ) 109 { 110 $GLOBALS['wpdb']->query( 'ALTER TABLE `' . $blog->table( $entity ) . '` AUTO_INCREMENT = ' . (string) $new ); 78 111 } 79 112 } … … 96 129 return $this->republished; 97 130 } 98 } 99 100 public function __set( $key, $value ) 101 { 102 if ( $key === 'consolidated' ) 103 { 104 if ( $value === true ) 105 { 106 $this->consolidated = true; 107 } 108 109 if ( $value === false ) 110 { 111 $this->consolidated = false; 112 113 if ( !empty( $this->republished ) ) 114 { 115 $this->set_auto_increment( $this->republished[0] + 1 ); 116 } 117 } 118 } 119 } 120 121 private function union( $table ) 122 { 123 if ( !$this->consolidated && empty( $this->republished ) ) 131 132 if ( $key === 'post_types' ) 133 { 134 return $this->post_types; 135 } 136 } 137 138 public function union( $table ) 139 { 140 // No need to do anything if not consolidated and no republished posts. 141 if ( !$this->consolidated && ( empty( $this->republished ) || empty( $post_cols = array_keys( WP_Super_Network::TABLES_TO_REPLACE[ $table ], 'posts', true ) ) ) ) 124 142 { 125 143 return $GLOBALS['wpdb']->__get( $table ); … … 131 149 { 132 150 $where = array(); 133 134 if ( $this->consolidated ) 135 { 136 if ( !empty( $this->collisions ) ) 137 { 138 $where[] = self::ID_COLS[ $table ] . ' not in (' . implode( ', ', $this->collisions ) . ')'; 139 } 140 141 if ( $table === 'posts' && !empty( $this->post_types ) && !$blog->is_network() ) 142 { 143 $where[] = 'post_type not in (\'' . implode( '\', \'', $this->post_types ) . '\')'; 144 } 145 } 146 else 147 { 151 $this->exclude( $where, $table, $blog ); 152 153 if ( !$this->consolidated ) 154 { 155 // Add republished posts. 148 156 if ( $blog->table( $table ) !== $GLOBALS['wpdb']->__get( $table ) ) 149 157 { 150 $where[] = self::ID_COLS[ $table ] . ' in (' . implode( ', ', $this->republished ) . ')'; 158 foreach ( $post_cols as $col ) 159 { 160 // Post parent is currently not being handled. 161 $col === 'post_parent' or $where[] = '`' . $col . '` IN (' . implode( ', ', $this->republished ) . ')'; 162 } 151 163 } 152 164 } 153 165 154 $tables[] = 'select * from ' . $blog->table( $table ) . ( empty( $where ) ? '' : ( ' where ' . implode( ' and ', $where ) ) ); 155 } 156 157 return implode( ' union ', $tables ); 166 $tables[] = 'SELECT * FROM `' . $blog->table( $table ) . ( empty( $where ) ? '`' : ( '` WHERE ' . implode( ' AND ', $where ) ) ); 167 } 168 169 return implode( ' UNION ALL ', $tables ); 170 } 171 172 public function exclude( &$where, $table, $blog, $alias = '' ) 173 { 174 if ( $this->consolidated ) 175 { 176 empty( $alias ) or $alias .= '.'; 177 178 // Exclude any entities involved in collisions. 179 foreach ( array_keys( $this->collisions ) as $entity ) 180 { 181 if ( !empty( $this->collisions[ $entity ] ) ) 182 { 183 foreach ( array_keys( WP_Super_Network::TABLES_TO_REPLACE[ $table ], $entity, true ) as $col ) 184 { 185 $where[] = $alias . '`' . $col . '` NOT IN (' . implode( ', ', $this->collisions[ $entity ] ) . ')'; 186 } 187 } 188 } 189 190 // Exclude network-based post types. 191 if ( $table === 'posts' && !empty( $this->post_types ) && !$blog->is_network() ) 192 { 193 $where[] = $alias . '`post_type` NOT IN (\'' . implode( '\', \'', $this->post_types ) . '\')'; 194 } 195 } 158 196 } 159 197 160 198 public function report_collisions() 161 199 { 162 if ( $this->consolidated && !empty( $this->collisions ) && current_user_can( 'manage_network_options' ) ) 163 { 164 echo '<div class="notice notice-error"><p><strong>WP Super Network detected ' . count( $this->collisions ) . ' post ID collisions</strong> across your network! These posts have been temporarily hidden. To access them again, please turn off consolidated mode.</p></div>'; 200 $term_collisions = count( $this->collisions['term_taxonomy'] ) + count( $this->collisions['terms'] ); 201 202 if ( $this->consolidated && ( !empty( $this->collisions['posts'] ) || $term_collisions > 0 || !empty( $this->collisions['comments'] ) ) && current_user_can( 'manage_network_options' ) ) 203 { 204 echo '<div class="notice notice-error"><p><strong>WP Super Network detected '; 205 206 echo empty( $this->collisions['posts'] ) ? '' : count( $this->collisions['posts'] ) . ' post ID collisions'; 207 208 if ( !empty( $this->collisions['posts'] ) && $term_collisions > 0 ) 209 { 210 echo empty( $this->collisions['comments'] ) ? ' and ' : ', '; 211 } 212 213 echo $term_collisions > 0 ? $term_collisions . ' taxonomy term ID collisions' : ''; 214 215 if ( !empty( $this->collisions['comments'] ) ) 216 { 217 echo ( !empty( $this->collisions['posts'] ) || $term_collisions > 0 ) ? ' and ' : ''; 218 echo count( $this->collisions['comments'] ) . ' comment ID collisions'; 219 } 220 221 echo '</strong> across your network! These entities have been temporarily hidden. To access them again, please turn off consolidated mode.</p></div>'; 165 222 } 166 223 } … … 169 226 * Constructor. 170 227 * 171 * Constructs the network.228 * Queries sent during this method do not get modified, as the filter is applied later. 172 229 * 173 230 * @since 1.0.4 … … 179 236 foreach ( get_sites( 'network_id=' . $network->id ) as $site ) 180 237 { 181 array_push( $this->blogs, new Blog( $site ) ); 182 } 183 184 add_filter( 'admin_footer', array( $this, 'report_collisions' ) ); 185 186 $this->parser = new \PHPSQLParser\PHPSQLParser(); 187 $this->creator = new \PHPSQLParser\PHPSQLCreator(); 188 189 $this->collisions = $GLOBALS['wpdb']->get_col( 'select ID from (' . $this->union( 'posts' ) . ') posts group by ID having count(*) > 1 order by ID asc' ); 190 $this->republished = $GLOBALS['wpdb']->get_col( 'select post_id from (' . $this->union( 'postmeta' ) . ') postmeta where meta_key = \'_supernetwork_share\' order by post_id desc' ); 191 $this->post_types = array_keys( (array) get_option( 'supernetwork_post_types' ) ); 238 $this->blogs[ (int) $site->blog_id ] = new Blog( $site ); 239 } 192 240 193 241 $this->page = new Settings_Page( … … 209 257 210 258 global $wpdb; 211 $results = $wpdb->get_results( " select distinct option_name from $wpdb->options where option_name not like '\_%' and option_name not like 'supernetwork\_%' order by option_name" );259 $results = $wpdb->get_results( "SELECT DISTINCT `option_name` FROM $wpdb->options WHERE `option_name` NOT LIKE '\_%' AND `option_name` NOT LIKE 'supernetwork\_%' ORDER BY `option_name`" ); 212 260 $labels = array(); 213 261 … … 252 300 'Consolidated Mode', 253 301 'Turn on consolidated mode?', 254 '<strong>Warning:</strong> Consolidated mode is highly unstable. It is strongly suggested that you have a REGULAR backup regime in place for your database, before activatingconsolidated mode.'302 '<strong>Warning:</strong> Consolidated mode is recommended for fresh networks. If you have pre-existing data (e.g. posts and pages) on your network, some of it may not be compatible with consolidated mode.' 255 303 ); 256 304 … … 263 311 } 264 312 313 public function add_new_post( $hook_suffix ) 314 { 315 $post_type = empty( $_GET['post_type'] ) ? 'post' : $_GET['post_type']; 316 317 if ( $hook_suffix === 'edit.php' && $this->consolidated && !in_array( $post_type, $this->post_types, true ) ) 318 { 319 wp_enqueue_script( 320 'supernetwork-post-new', 321 SUPER_NETWORK_URL . 'assets/js/post-new.js', 322 array(), 323 null, 324 true 325 ); 326 327 $blogs = array(); 328 $type = get_post_type_object( $post_type ); 329 $edit = isset( $post_type ) ? $type->cap->edit_posts : 'do_not_allow'; 330 $create = isset( $post_type ) ? $type->cap->create_posts : 'do_not_allow'; 331 332 foreach ( $this->blogs as $blog ) 333 { 334 $id = $blog->id; 335 336 if ( current_user_can_for_blog( $id, $edit ) && current_user_can_for_blog( $id, $create ) ) 337 { 338 $blogs[ $id ] = $blog->name; 339 } 340 } 341 342 wp_add_inline_script( 343 'supernetwork-post-new', 344 'var blogs = ' . json_encode( $blogs ) . '; var currentId = "' . get_current_blog_id() . '";', 345 'before' 346 ); 347 } 348 } 349 265 350 public function post_types( $args, $field ) 266 351 { … … 276 361 } 277 362 278 public function register_pages() 363 private function push_meta_ids( $meta_ids, $object_id ) 364 { 365 $this->meta_ids[] = array( $meta_ids, $object_id ); 366 } 367 368 public function meta_ids() 369 { 370 return empty( $this->meta_ids ) ? array() : $this->meta_ids[ count( $this->meta_ids ) - 1 ][0]; 371 } 372 373 public function meta_object_id() 374 { 375 return empty( $this->meta_ids ) ? 0 : $this->meta_ids[ count( $this->meta_ids ) - 1 ][1]; 376 } 377 378 public function pop_meta_ids() 379 { 380 array_pop( $this->meta_ids ); 381 } 382 383 public function delete_meta( $meta_ids, $object_id ) 384 { 385 $this->push_meta_ids( $meta_ids, $object_id ); 386 return $meta_ids; 387 } 388 389 public function register() 279 390 { 280 391 $this->page->register(); 392 393 add_filter( 'post_type_link', array( $this, 'intercept_permalink_for_post' ), 10, 2 ); 394 add_filter( 'post_link', array( $this, 'intercept_permalink_for_post' ), 10, 2 ); 395 add_filter( 'page_link', array( $this, 'intercept_permalink' ), 10, 2 ); 396 add_filter( 'preview_post_link', array( $this, 'intercept_preview_link' ), 10, 2 ); 397 add_filter( 'supernetwork_preview_link', array( $this, 'replace_preview_link' ), 10, 2 ); 398 add_filter( 'user_has_cap', array( $this, 'intercept_capability' ), 10, 4 ); 399 add_filter( 'pre_handle_404', array( $this, 'singular_access' ), 10, 2 ); 400 add_action( 'wp', array( $this, 'preview_access' ) ); 401 add_filter( 'query', array( $this, 'intercept_query' ), 10, 2 ); 402 add_filter( 'wp_insert_post', array( $this, 'shared_auto_increment' ), 10, 3 ); 403 add_filter( 'wp_insert_comment', array( $this, 'auto_increment_comments' ), 10, 2 ); 404 add_filter( 'created_term', array( $this, 'auto_increment_terms' ), 10, 4 ); 405 add_filter( 'added_term_relationship', array( $this, 'auto_increment_term_relationships' ), 10, 3 ); 406 add_filter( 'admin_enqueue_scripts', array( $this, 'add_new_post' ) ); 407 add_filter( 'admin_footer', array( $this, 'report_collisions' ) ); 408 add_filter( 'delete_comment_meta', array( $this, 'delete_meta' ), 10, 2 ); 409 add_filter( 'delete_post_meta', array( $this, 'delete_meta' ), 10, 2 ); 410 add_filter( 'delete_term_meta', array( $this, 'delete_meta' ), 10, 2 ); 411 412 // Comments must be queried before posts so as not to mask any comment ID collisions. 413 // Taxonomy terms must be queried before terms so as not to mask any taxonomy term ID collisions. 414 foreach ( array_keys( $this->collisions ) as $entity ) 415 { 416 // `ID` comes before `post_parent` in the `posts` sub-array, so `ID` will be correctly returned. 417 $id = array_search( $entity, WP_Super_Network::TABLES_TO_REPLACE[ $entity ], true ); 418 419 $this->collisions[ $entity ] = $GLOBALS['wpdb']->get_col( 'SELECT `' . $id . '` FROM `' . $GLOBALS['wpdb']->__get( $entity ) . '` GROUP BY `' . $id . '` HAVING COUNT(*) > 1 ORDER BY `' . $id . '` ASC' ); 420 } 421 422 $consolidated = !empty( get_option( 'supernetwork_consolidated', array() )['consolidated'] ); 423 424 if ( $consolidated ) 425 { 426 $this->post_types = array_keys( get_option( 'supernetwork_post_types', array() ) ); 427 } 428 else 429 { 430 $extra_where = ''; 431 $extra_where_2 = ''; 432 $relationships = $GLOBALS['wpdb']->term_relationships; 433 $taxonomy = $GLOBALS['wpdb']->term_taxonomy; 434 435 // The `AND 1=1` is to avoid a bug in the SQL parser caused by consecutive brackets. 436 empty( $this->collisions['comments'] ) or $extra_where .= ' AND `post_id` NOT IN (SELECT `comment_post_ID` FROM `' . $GLOBALS['wpdb']->comments . '` WHERE `comment_ID` IN (' . implode( ', ', $this->collisions[ 'comments' ] ) . ') AND 1=1)'; 437 438 if ( !empty( $this->collisions['term_taxonomy'] ) ) 439 { 440 $extra_where .= ' AND `post_id` NOT IN (SELECT `object_id` FROM `' . $relationships . '` WHERE `term_taxonomy_id` IN (' . implode( ', ', $this->collisions[ 'term_taxonomy' ] ) . ') AND 1=1)'; 441 $extra_where_2 = ' AND `' . $relationships . '`.`term_taxonomy_id` NOT IN (' . implode( ', ', $this->collisions[ 'term_taxonomy' ] ) . ')'; 442 } 443 444 empty( $this->collisions['terms'] ) or $extra_where .= ' AND `post_id` NOT IN (SELECT `object_id` FROM `' . $relationships . '` LEFT JOIN `' . $taxonomy . '` ON `' . $relationships . '`.`term_taxonomy_id` = `' . $taxonomy . '`.`term_taxonomy_id` WHERE `term_id` IN (' . implode( ', ', $this->collisions[ 'terms' ] ) . ')' . $extra_where_2 . ' AND 1=1)'; 445 446 // This query requires post collisions, but not the other collisions, to be recorded. 447 $old_comment_collisions = $this->collisions['comments']; 448 $old_taxonomy_term_collisions = $this->collisions['term_taxonomy']; 449 $old_term_collisions = $this->collisions['terms']; 450 451 foreach ( array( 'comments', 'term_taxonomy', 'terms' ) as $entity ) $this->collisions[ $entity ] = array(); 452 453 $this->republished = $GLOBALS['wpdb']->get_col( 'SELECT DISTINCT `post_id` FROM `' . $GLOBALS['wpdb']->postmeta . '` WHERE `meta_key` = \'_supernetwork_share\'' . $extra_where . ' ORDER BY `post_id` DESC' ); 454 455 $this->collisions['comments'] = $old_comment_collisions; 456 $this->collisions['term_taxonomy'] = $old_taxonomy_term_collisions; 457 $this->collisions['terms'] = $old_term_collisions; 458 } 459 460 $this->consolidated = $consolidated; 461 462 if ( !$this->consolidated && !empty( $this->republished ) ) 463 { 464 $this->set_auto_increment( $this->republished[0] + 1 ); 465 } 466 } 467 468 /** 469 * Selects an entity type to be first in line for collision elimination, based on a calculated score. 470 * 471 * The score for each entity type is the median ID of its collisions, divided by the median ID overall. 472 * A score of 1 means the collisions are evenly distributed. 473 * A lower score means the collisions are concentrated on the lower ID's, which are more likely to be older entities with more references and importance. 474 * 475 * The entity type with the lowest score is selected for collision elimination. 476 * 477 * @since 1.2.0 478 */ 479 private function get_entity_by_median_score() 480 { 481 // Temporarily empty collisions. 482 $old = $this->collisions; 483 $this->collisions = WP_Super_Network::ENTITIES_TO_REPLACE; 484 $scores = WP_Super_Network::ENTITIES_TO_REPLACE; 485 486 foreach ( $scores as $entity => &$score ) 487 { 488 // `ID` comes before `post_parent` in the `posts` sub-array, so `ID` will be correctly returned. 489 $id = array_search( $entity, WP_Super_Network::TABLES_TO_REPLACE[ $entity ], true ); 490 491 $collisions = $GLOBALS['wpdb']->get_col( 'SELECT `' . $id . '` FROM `' . $GLOBALS['wpdb']->__get( $entity ) . '` WHERE `' . $id . '` IN (SELECT `' . $id . '` FROM `' . $GLOBALS['wpdb']->__get( $entity ) . '` GROUP BY `' . $id . '` HAVING COUNT(*) > 1) ORDER BY `' . $id . '` ASC' ); 492 $all = $GLOBALS['wpdb']->get_col( 'SELECT `' . $id . '` FROM `' . $GLOBALS['wpdb']->__get( $entity ) . '` ORDER BY `' . $id . '` ASC' ); 493 494 $score = empty( $collisions ) ? PHP_FLOAT_MAX : $collisions[ floor( count( $collisions ) / 2 ) ] / $all[ floor( count( $all ) / 2 ) ]; 495 } 496 497 $this->collisions = $old; 498 return array_search( min( $scores ), $scores ); 499 } 500 501 /** 502 * Force deletes a term from a taxonomy. 503 * 504 * @since 1.2.0 505 */ 506 private function force_delete_taxonomy_term( $term_id, $taxonomy ) 507 { 508 // Temporarily register taxonomy to force deletion. 509 if ( !( $exists = taxonomy_exists( $taxonomy ) ) ) register_taxonomy( $taxonomy, array() ); 510 511 // 0 is returned if the default term was not deleted. 512 $success = 0 !== wp_delete_term( $term_id, $taxonomy ); 513 514 if ( !$exists ) unregister_taxonomy( $taxonomy ); 515 return $success; 281 516 } 282 517 … … 288 523 if ( $this->consolidated ) 289 524 { 290 echo '<h2>Post ID Collisions</h2>'; 291 292 if ( !empty( $this->collisions ) && isset( $_POST['supernetwork_post_collision_' . $this->collisions[0] ] ) ) 293 { 294 $this->consolidated = false; 295 296 foreach ( $this->blogs as $blog ) 525 $entity = $this->get_entity_by_median_score(); 526 527 $this->consolidated = false; 528 529 echo '<h2>ID Collisions</h2>'; 530 531 if ( !empty( $this->collisions[ $entity ] ) && isset( $_POST[ 'supernetwork_' . $entity . '_collision_' . $this->collisions[ $entity ][0] ] ) ) 532 { 533 $success = true; 534 535 foreach ( $this->blogs as $blog ) 536 { 537 if ( $blog->id !== (int) explode( '_', $_POST[ 'supernetwork_' . $entity . '_collision_' . $this->collisions[ $entity ][0] ] )[0] ) 297 538 { 298 539 switch_to_blog( $blog->id ); 299 300 if ( empty( $GLOBALS['wpdb']->get_col( 'select ID from ' . $blog->table( 'posts' ) . ' where guid = \'' . $_POST['supernetwork_post_collision_' . $this->collisions[0] ] . '\' limit 1' ) ) ) 540 541 if ( $entity === 'comments' ) wp_delete_comment( (int) $this->collisions['comments'][0], true ); 542 if ( $entity === 'posts' ) wp_delete_post( (int) $this->collisions['posts'][0], true ); 543 544 if ( $entity === 'term_taxonomy' ) 301 545 { 302 wp_delete_post( (int) $this->collisions[0], true ); 546 $term = $GLOBALS['wpdb']->get_row( 'SELECT `taxonomy`, `term_id` FROM `' . $GLOBALS['wpdb']->term_taxonomy . '` WHERE `term_taxonomy_id` = ' . $this->collisions['term_taxonomy'][0], ARRAY_A ); 547 548 if ( isset( $term['term_id'] ) && isset( $term['taxonomy'] ) ) 549 { 550 $success = $this->force_delete_taxonomy_term( (int) $term['term_id'], $term['taxonomy'] ) && $success; 551 } 552 } 553 554 if ( $entity === 'terms' ) 555 { 556 $results = $GLOBALS['wpdb']->get_results( 'SELECT `taxonomy`, `term_id` FROM `' . $GLOBALS['wpdb']->term_taxonomy . '` WHERE `term_id` = ' . $this->collisions['terms'][0] ); 557 558 foreach ( $results as $result ) 559 { 560 $success = $this->force_delete_taxonomy_term( (int) $result['term_id'], $result['taxonomy'] ) && $success; 561 } 303 562 } 304 563 305 564 restore_current_blog(); 306 565 } 307 566 } 567 568 // Start again if collision was eliminated. 569 if ( $success ) 570 { 571 array_shift( $this->collisions[ $entity ] ); 308 572 $this->consolidated = true; 309 array_shift( $this->collisions ); 310 } 311 312 if ( empty( $this->collisions ) ) 313 { 314 echo '<p>No collisions on this network!</p>'; 315 } 316 else 317 { 318 $old_collisions = $this->collisions; 319 $old_post_types = $this->post_types; 320 321 $this->collisions = array(); 322 $this->post_types = array(); 323 324 echo '<p>Consolidated mode is designed for fresh networks. When activated on an existing network, a large number of ID collisions are inevitable. However, you may be able to eliminate some collisions when the ID refers to a post of low importance, such as a revision or autosave.</p>'; 325 echo '<p>The below table allows you to eliminate post ID collisions, one ID at a time. For each ID, you must select just ONE post to keep. All others with the same ID will be immediately and irretrievably deleted.</p>'; 326 327 echo '<form method="post" action="">'; 328 echo '<table class="widefat">'; 329 echo '<thead><tr><th scope="col">Keep?</th><th scope="col">GUID</th><th scope="col">Post Title</th><th scope="col">Post Preview</th><th scope="col">Post Type</th><th scope="col">Post Status</th></tr></thead>'; 330 echo '<tbody>'; 331 332 foreach ( $GLOBALS['wpdb']->get_results( 'select guid, post_title, substring(post_content, 1, 500) as post_preview, post_type, post_status from ' . $GLOBALS['wpdb']->posts . ' where ID = ' . $old_collisions[0], ARRAY_A ) as $site ) 573 $entity = $this->get_entity_by_median_score(); 574 $this->consolidated = false; 575 } 576 } 577 578 if ( empty( $this->collisions[ $entity ] ) ) 579 { 580 echo '<p>No collisions on this network!</p>'; 581 } 582 else 583 { 584 // `ID` comes before `post_parent` in the `posts` sub-array, so `ID` will be correctly returned. 585 $id = array_search( $entity, WP_Super_Network::TABLES_TO_REPLACE[ $entity ], true ); 586 $id = 'terms' === $entity ? 't.' . $id : $id; 587 588 $fields = array( 589 'comments' => array( 590 'post_title', 591 'comment_content', 592 'comment_author', 593 'comment_author_email' 594 ), 595 'posts' => array( 596 'post_title', 597 'post_content', 598 'post_type', 599 'post_status' 600 ), 601 'term_taxonomy' => array( 602 'name', 603 'description', 604 'taxonomy', 605 'count' 606 ), 607 'terms' => array( 608 'name', 609 'description', 610 'taxonomy', 611 'count' 612 ) 613 ); 614 615 $labels = array( 616 'comments' => array( 617 'Site Containing Post/Comment', 618 'Post Containing Comment', 619 'Comment Preview', 620 'Comment Author', 621 'Comment Author Email' 622 ), 623 'posts' => array( 624 'Site Containing Post', 625 'Post Title', 626 'Post Preview', 627 'Post Type', 628 'Post Status' 629 ), 630 'term_taxonomy' => array( 631 'Site Containing Taxonomy Term', 632 'Taxonomy Term Name', 633 'Taxonomy Term Description', 634 'Taxonomy', 635 'Post Count' 636 ), 637 'terms' => array( 638 'Site Containing Taxonomy Term', 639 'Taxonomy Term Name', 640 'Taxonomy Term Description', 641 'Taxonomy', 642 'Post Count' 643 ) 644 ); 645 646 echo '<p>Consolidated mode is designed for fresh networks. When activated on an existing network, a large number of ID collisions are inevitable. However, you may be able to eliminate some collisions when the ID refers to a post of low importance, such as a revision or autosave.</p>'; 647 echo '<p>The below tables allow you to eliminate post, taxonomy term and comment ID collisions, one ID at a time. For each ID, you must select just ONE entity to keep. All others with the same ID will be immediately and irretrievably deleted.</p>'; 648 649 if ( in_array( $entity, array( 'term_taxonomy', 'terms' ), true ) ) echo '<div class="notice notice-warning inline"><p><strong>Note:</strong> Default taxonomy terms can not be deleted! If a default taxonomy term is in a collision, you must go to Writing Settings and select a new default term for the taxonomy before you can eliminate the collision.</p></div>'; 650 651 echo '<form method="post" action="">'; 652 echo '<table class="widefat">'; 653 echo '<thead><tr><th scope="col">Keep?</th><th scope="col">' . $labels[ $entity ][0] . '</th><th scope="col">' . $labels[ $entity ][1] . '</th><th scope="col">' . $labels[ $entity ][2] . '</th><th scope="col">' . $labels[ $entity ][3] . '</th><th scope="col">' . $labels[ $entity ][4] . '</th></tr></thead>'; 654 echo '<tbody>'; 655 656 foreach ( $this->blogs as $blog ) 657 { 658 $tables = array( 659 'comments' => $blog->table( 'comments' ) . ' LEFT JOIN ' . $blog->table( 'posts' ) . ' ON comment_post_ID = ID', 660 'posts' => $blog->table( 'posts' ), 661 'term_taxonomy' => $blog->table( 'term_taxonomy' ) . ' tt LEFT JOIN ' . $blog->table( 'terms' ) . ' t ON tt.term_id = t.term_id', 662 'terms' => $blog->table( 'term_taxonomy' ) . ' tt LEFT JOIN ' . $blog->table( 'terms' ) . ' t ON tt.term_id = t.term_id' 663 ); 664 665 $rows = $GLOBALS['wpdb']->get_results( 'SELECT `' . $fields[ $entity ][0] . '`, SUBSTRING(`' . $fields[ $entity ][1] . '`, 1, 500) AS `' . $fields[ $entity ][1] . '_preview`, `' . $fields[ $entity ][2] . '`, `' . $fields[ $entity ][3] . '` FROM ' . $tables[ $entity ] . ' WHERE `' . $id . '` = ' . $this->collisions[ $entity ][0], ARRAY_A ); 666 667 // There may be more than one row if terms are not split. 668 foreach ( $rows as $key => $row ) 333 669 { 334 670 echo '<tr>'; 335 echo '<td><input type="radio" id="supernetwork__' . esc_ textarea( $site['guid'] ) . '" value="' . esc_textarea( $site['guid'] ) . '" name="supernetwork_post_collision_' . $old_collisions[0] . '"></td>';336 echo '<td><label for="supernetwork__' . esc_ textarea( $site['guid'] ) . '">' . esc_textarea( $site['guid']) . '</label></td>';337 echo '<td><label for="supernetwork__' . esc_ textarea( $site['guid'] ) . '">' . esc_textarea( $site['post_title'] ) . '</label></td>';338 echo '<td><label for="supernetwork__' . esc_ textarea( $site['guid'] ) . '">' . esc_textarea( $site['post_preview'] ) . '</label></td>';339 echo '<td><label for="supernetwork__' . esc_ textarea( $site['guid'] ) . '">' . esc_textarea( $site['post_type'] ) . '</label></td>';340 echo '<td><label for="supernetwork__' . esc_ textarea( $site['guid'] ) . '">' . esc_textarea( $site['post_status'] ) . '</label></td>';671 echo '<td><input type="radio" id="supernetwork__' . esc_attr( $blog->id ) . '_' . $key . '" value="' . esc_attr( $blog->id ) . '" name="supernetwork_' . $entity . '_collision_' . $this->collisions[ $entity ][0] . '"></td>'; 672 echo '<td><label for="supernetwork__' . esc_attr( $blog->id ) . '_' . $key . '">' . esc_html( $blog->name ) . '</label></td>'; 673 echo '<td><label for="supernetwork__' . esc_attr( $blog->id ) . '_' . $key . '">' . esc_html( $row[ $fields[ $entity ][0] ] ) . '</label></td>'; 674 echo '<td><label for="supernetwork__' . esc_attr( $blog->id ) . '_' . $key . '">' . esc_html( $row[ $fields[ $entity ][1] . '_preview' ] ) . '</label></td>'; 675 echo '<td><label for="supernetwork__' . esc_attr( $blog->id ) . '_' . $key . '">' . esc_html( $row[ $fields[ $entity ][2] ] ) . '</label></td>'; 676 echo '<td><label for="supernetwork__' . esc_attr( $blog->id ) . '_' . $key . '">' . esc_html( $row[ $fields[ $entity ][3] ] ) . '</label></td>'; 341 677 echo '</tr>'; 342 678 } 343 344 $this->collisions = $old_collisions; 345 $this->post_types = $old_post_types;346 347 echo '</tbody></table>';348 submit_button( 'Keep Selected and Delete All Others' );349 echo '</form>'; 350 }351 }679 } 680 681 echo '</tbody></table>'; 682 submit_button( 'Keep Selected and Delete All Others' ); 683 echo '</form>'; 684 } 685 686 $this->consolidated = true; 687 } 352 688 else 353 689 { … … 419 755 public function intercept_query( $query ) 420 756 { 421 try 422 { 423 $parsed = $this->parser->parse( $query ); 424 425 // Insert, update and delete queries are not currently modified. 426 return $this->modify_query( $parsed ) ? $this->creator->create( $parsed ) : $query; 427 } 428 catch ( \PHPSQLParser\exceptions\UnsupportedFeatureException $uf ) 429 { 430 return $query; 431 } 432 catch ( \PHPSQLParser\exceptions\UnableToCreateSQLException $utcsql ) 433 { 434 return $query; 435 } 436 } 437 438 private function modify_query( &$parsed ) 439 { 440 if ( in_array( array_keys( $parsed )[0], array( 'UNION', 'SELECT' ), true ) ) 441 { 442 if ( isset( $parsed['UNION'] ) ) 443 { 444 foreach ( $parsed['UNION'] as &$query ) 445 { 446 $this->modify_query( $query ); 447 } 448 } 449 else 450 { 451 if ( isset( $parsed['SELECT'] ) && isset( $parsed['FROM'] ) ) 452 { 453 foreach ( $parsed['FROM'] as &$from ) 454 { 455 if ( !empty( $from['sub_tree'] ) ) 456 { 457 $this->modify_query( $from['sub_tree'] ); 458 } 459 else 460 { 461 foreach ( array_keys( self::ID_COLS ) as $table ) 462 { 463 $local_table = $GLOBALS['wpdb']->__get( $table ); 464 465 if ( isset( $from['table'] ) && $from['table'] === $local_table ) 466 { 467 if ( ( $union = $this->union( $table ) ) === $from['table'] ) 468 { 469 continue; 470 } 471 472 $from['expr_type'] = 'subquery'; 473 $from['sub_tree'] = $this->parser->parse( $union ); 474 475 if ( false === $from['alias'] ) 476 { 477 $from['alias'] = array( 478 'as' => false, 479 'name' => $local_table, 480 'no_quotes' => array( 481 'delim' => false, 482 'parts' => array( $local_table ) 483 ), 484 'base_expr' => $local_table 485 ); 486 } 487 488 unset( $from['table'] ); 489 break; 490 } 491 } 492 } 493 } 494 495 foreach ( array( 'WHERE', 'HAVING' ) as $clause ) 496 { 497 if ( isset( $parsed[ $clause ] ) ) 498 { 499 foreach ( $parsed[ $clause ] as &$subclause ) 500 { 501 if ( !empty( $subclause['sub_tree'] ) ) 502 { 503 $this->modify_query( $subclause['sub_tree'] ); 504 } 505 } 506 } 507 } 508 } 509 } 510 511 return true; 512 } 513 514 return false; 757 $cache = wp_cache_get( 'supernetwork_queries', $query ); 758 759 if ( $cache ) 760 { 761 return $cache->transformed; 762 } 763 else 764 { 765 $transformed = new Query( $query, $this ); 766 wp_cache_set( 'supernetwork_queries', $transformed, $query ); 767 return $transformed->transformed; 768 } 515 769 } 516 770 517 771 public function intercept_permalink( $permalink, $post_ID ) 518 772 { 519 // Prevent infinite loop 520 if ( empty( $GLOBALS['_wp_switched_stack'] ) ) 521 { 522 if ( !is_null( $blog = $this->get_blog( $post_ID ) ) ) 523 { 524 switch_to_blog( $blog->id ); 525 $permalink = get_permalink( $post_ID ); 526 restore_current_blog(); 527 } 773 if ( !doing_filter( 'supernetwork_preview_link' ) && !is_null( $blog = $this->get_blog( $post_ID ) ) ) 774 { 775 switch_to_blog( $blog->id ); 776 $permalink = get_permalink( $post_ID ); 777 restore_current_blog(); 528 778 } 529 779 … … 536 786 } 537 787 538 public function intercept_capability_write( $caps, $cap, $user_id, $args ) 539 { 540 if ( in_array( $cap, array( 'delete_post', 'edit_post', 'publish_post' ), true ) ) 541 { 542 if ( !is_null( $blog = $this->get_blog( get_post( $args[0] )->ID ) ) && get_current_blog_id() !== $blog->id ) 543 { 544 return array( 'do_not_allow' ); 545 } 546 } 547 548 return $caps; 549 } 550 551 public function intercept_capability_read( $allcaps, $caps, $args, $user ) 552 { 553 if ( $args[0] === 'read_post' ) 788 public function intercept_preview_link( $preview_link, $post ) 789 { 790 return doing_filter( 'supernetwork_preview_link' ) ? $preview_link : apply_filters( 'supernetwork_preview_link', $preview_link, $post ); 791 } 792 793 public function replace_preview_link( $preview_link, $post ) 794 { 795 $query_args = array(); 796 parse_str( parse_url( $preview_link, PHP_URL_QUERY ), $query_args ); 797 return get_preview_post_link( $post, array_intersect_key( $query_args, array( 'preview_nonce' => null, 'preview_id' => null ) ) ); 798 } 799 800 public function intercept_capability( $allcaps, $caps, $args, $user ) 801 { 802 if ( in_array( $args[0], array( 'delete_post', 'edit_post', 'publish_post', 'read_post' ), true ) ) 554 803 { 555 804 if ( !is_null( $blog = $this->get_blog( get_post( $args[2] )->ID ) ) ) … … 564 813 public function singular_access( $false, $wp_query ) 565 814 { 566 if ( $wp_query->is_singular() && !is_admin() && ( !defined( 'REST_REQUEST' ) || !REST_REQUEST ) && isset( $wp_query->post ) && !is_null( $blog = $this->get_blog( $wp_query->post->ID ) ) && get_current_blog_id() !== $blog->id)815 if ( $wp_query->is_singular() && !is_admin() && ( !defined( 'REST_REQUEST' ) || !REST_REQUEST ) && !is_preview() && isset( $wp_query->post ) && !is_null( $this->get_blog( $wp_query->post->ID ) ) ) 567 816 { 568 817 $wp_query->set_404(); … … 575 824 } 576 825 577 private function get_blog( $id ) 578 { 579 if ( !in_array( (string) $id, $this->collisions, true ) ) 580 { 826 public function preview_access() 827 { 828 if ( is_preview() && !is_null( $blog = $this->get_blog( get_the_ID() ) ) ) 829 { 830 switch_to_blog( $blog->id ); 831 } 832 } 833 834 public function get_blog( $id, $entity = 'posts' ) 835 { 836 if ( in_array( $entity, array_keys( WP_Super_Network::ENTITIES_TO_REPLACE ), true ) && !in_array( (string) $id, $this->collisions[ $entity ], true ) && ( $this->consolidated || !empty( $this->republished ) && $entity !== 'posts' || in_array( (string) $id, $this->republished, true ) ) ) 837 { 838 if ( isset( $this->blog_cache[ $entity ][ $id ] ) ) return get_current_blog_id() === $this->blog_cache[ $entity ][ $id ]->id ? null : $this->blog_cache[ $entity ][ $id ]; 839 840 $republished = null; 841 $extra_where = ''; 842 843 // `ID` comes before `post_parent` in the `posts` sub-array, so `ID` will be correctly returned. 844 $col = array_search( $entity, WP_Super_Network::TABLES_TO_REPLACE[ $entity ], true ); 845 $select = $entity === 'posts' ? 'post_type' : $col; 846 581 847 $old_consolidated = $this->consolidated; 582 848 $old_republished = $this->republished; … … 587 853 foreach ( $this->blogs as $blog ) 588 854 { 589 if ( !empty( $GLOBALS['wpdb']->get_col( 'select ID from ' . $blog->table( 'posts' ) . ' where ID = ' . $id . ' limit 1' ) ) ) 855 if ( !$old_consolidated ) 856 { 857 isset( $republished ) or $republished = '(' . implode( ', ', $old_republished ) . ')'; 858 859 // The `AND 1=1` is to avoid a bug in the SQL parser caused by consecutive brackets. 860 $entity !== 'comments' or $extra_where = ' AND `comment_post_ID` IN ' . $republished . ' AND 1=1'; 861 $entity !== 'term_taxonomy' or $extra_where = ' AND EXISTS (SELECT * FROM `' . $blog->table( 'term_relationships' ) . '` WHERE `term_taxonomy_id` = ' . $id . ' AND `object_id` IN ' . $republished . ' AND 1=1)'; 862 $entity !== 'terms' or $extra_where = ' AND EXISTS (SELECT * FROM `' . $blog->table( 'term_relationships' ) . '` WHERE `term_taxonomy_id` IN (SELECT `term_taxonomy_id` FROM ' . $blog->table( 'term_taxonomy' ) . ' WHERE `term_id` = ' . $id . ') AND `object_id` IN ' . $republished . ' AND 1=1)'; 863 } 864 865 $result = $GLOBALS['wpdb']->get_var( 'SELECT `' . $select . '` FROM `' . $blog->table( $entity ) . '` WHERE `' . $col . '` = ' . $id . $extra_where . ' LIMIT 1' ); 866 867 if ( !empty( $result ) ) 590 868 { 591 869 $this->consolidated = $old_consolidated; 592 870 $this->republished = $old_republished; 593 871 594 return $blog; 872 $this->blog_cache[ $entity ][ $id ] = $this->consolidated && in_array( $result, $this->post_types, true ) && !$blog->is_network() ? null : $blog; 873 return get_current_blog_id() === $blog->id ? null : $this->blog_cache[ $entity ][ $id ]; 595 874 } 596 875 } … … 600 879 } 601 880 602 return null; 881 return $this->blog_cache[ $entity ][ $id ] = null; 882 } 883 884 public function get_blog_by_id( $id ) 885 { 886 return isset( $this->blogs[ $id ] ) ? $this->blogs[ $id ] : null; 603 887 } 604 888 } -
wp-super-network/trunk/src/plugin.php
r2671848 r2864100 7 7 class WP_Super_Network 8 8 { 9 const ENTITIES_TO_REPLACE = array( 10 'comments' => array(), 11 'posts' => array(), 12 'term_taxonomy' => array(), 13 'terms' => array() 14 ); 15 16 const TABLES_TO_REPLACE = array( 17 'commentmeta' => array( 18 'comment_id' => 'comments' 19 ), 20 'comments' => array( 21 'comment_ID' => 'comments', 22 'comment_post_ID' => 'posts' 23 ), 24 'postmeta' => array( 25 'post_id' => 'posts' 26 ), 27 'posts' => array( 28 'ID' => 'posts', 29 'post_parent' => 'posts' 30 ), 31 'term_relationships' => array( 32 'object_id' => 'posts', 33 'term_taxonomy_id' => 'term_taxonomy' 34 ), 35 'term_taxonomy' => array( 36 'term_id' => 'terms', 37 'term_taxonomy_id' => 'term_taxonomy' 38 ), 39 'termmeta' => array( 40 'term_id' => 'terms' 41 ), 42 'terms' => array( 43 'term_id' => 'terms' 44 ) 45 ); 46 9 47 /** 10 48 * Static instance of the plugin. … … 45 83 } 46 84 47 function options( $value, $option)85 public static function options( $value, $option, $default ) 48 86 { 49 87 if ( !is_main_site() ) 50 88 { 51 switch_to_blog( get_main_site_id() ); 52 $value = get_option( $option ); 53 restore_current_blog(); 89 $main = get_main_site_id(); 90 91 if ( $main > 0 ) 92 { 93 switch_to_blog( $main ); 94 $value = get_option( $option, $default ); 95 restore_current_blog(); 96 } 97 else 98 { 99 if ( in_array( $option, array( 'supernetwork_consolidated', 'supernetwork_post_types', 'supernetwork_options' ), true ) ) 100 { 101 $value = $default; 102 } 103 } 104 105 if ( $value === false ) 106 { 107 add_filter( 'option_' . $option, '__return_false' ); 108 } 54 109 } 55 110 … … 57 112 } 58 113 114 public static function add_option( $option, $value ) 115 { 116 if ( !is_main_site() && has_filter( 'pre_option_' . $option, array( __CLASS__, 'options' ) ) ) 117 { 118 $main = get_main_site_id(); 119 120 if ( $main > 0 && current_user_can_for_blog( $main, 'manage_options' ) ) 121 { 122 switch_to_blog( $main ); 123 add_option( $option, $value ); 124 restore_current_blog(); 125 } 126 } 127 } 128 129 public static function update_option( $option, $old_value, $value ) 130 { 131 if ( !is_main_site() && has_filter( 'pre_option_' . $option, array( __CLASS__, 'options' ) ) ) 132 { 133 $main = get_main_site_id(); 134 135 if ( $main > 0 && current_user_can_for_blog( $main, 'manage_options' ) ) 136 { 137 switch_to_blog( $main ); 138 update_option( $option, $value ); 139 restore_current_blog(); 140 } 141 } 142 } 143 59 144 /** 60 145 * Launch the initialization process. … … 64 149 public function run() 65 150 { 151 // Complete this before accessing the option on next line 152 add_filter( 'pre_option_supernetwork_options', array( __CLASS__, 'options' ), 10, 3 ); 153 add_filter( 'pre_option_supernetwork_post_types', array( __CLASS__, 'options' ), 10, 3 ); 154 add_filter( 'pre_option_supernetwork_consolidated', array( __CLASS__, 'options' ), 10, 3 ); 155 156 foreach ( get_option( 'supernetwork_options', array() ) as $option => $val ) 157 { 158 if ( $val && strpos( $option, '_' ) !== 0 && strpos( $option, 'supernetwork_' ) !== 0 ) 159 { 160 add_filter( 'pre_option_' . $option, array( __CLASS__, 'options' ), 10, 3 ); 161 } 162 } 163 164 add_filter( 'add_option', array( __CLASS__, 'add_option' ), 10, 2 ); 165 add_filter( 'update_option', array( __CLASS__, 'update_option' ), 10, 3 ); 166 167 // Disable querying of meta ID. See issue #10 168 add_filter( 'update_comment_metadata_by_mid', '__return_false' ); 169 add_filter( 'update_post_metadata_by_mid', '__return_false' ); 170 add_filter( 'update_term_metadata_by_mid', '__return_false' ); 171 add_filter( 'delete_comment_metadata_by_mid', '__return_false' ); 172 add_filter( 'delete_post_metadata_by_mid', '__return_false' ); 173 add_filter( 'delete_term_metadata_by_mid', '__return_false' ); 174 175 $this->network->register(); 176 66 177 // Load functions 67 add_filter( 'post_type_link', array( $this->network, 'intercept_permalink_for_post' ), 10, 2 );68 add_filter( 'post_link', array( $this->network, 'intercept_permalink_for_post' ), 10, 2 );69 add_filter( 'page_link', array( $this->network, 'intercept_permalink' ), 10, 2 );70 add_filter( 'map_meta_cap', array( $this->network, 'intercept_capability_write' ), 10, 4 );71 add_filter( 'user_has_cap', array( $this->network, 'intercept_capability_read' ), 10, 4 );72 add_filter( 'pre_handle_404', array( $this->network, 'singular_access' ), 10, 2 );73 add_filter( 'query', array( $this->network, 'intercept_query' ), 10, 2 );74 add_filter( 'wp_insert_post', array( $this->network, 'shared_auto_increment' ), 10, 3 );75 178 add_filter( 'network_admin_menu', array( $this, 'summary' ) ); 76 77 if ( is_main_site() ) $this->network->register_pages();78 79 // Complete this before accessing the option on next line80 add_filter( 'pre_option_supernetwork_options', array( $this, 'options' ), 10, 2 );81 add_filter( 'pre_option_supernetwork_post_types', array( $this, 'options' ), 10, 2 );82 add_filter( 'pre_option_supernetwork_consolidated', array( $this, 'options' ), 10, 2 );83 84 foreach ( (array) get_option( 'supernetwork_options' ) as $option => $val )85 {86 if ( $val && strpos( $option, '_' ) !== 0 && strpos( $option, 'supernetwork_' ) !== 0 )87 {88 add_filter( 'pre_option_' . $option, array( $this, 'options' ), 10, 2 );89 }90 }91 92 $this->network->consolidated = !empty( get_option( 'supernetwork_consolidated' )['consolidated'] );93 179 94 180 if ( !$this->network->consolidated ) … … 154 240 if ( current_user_can( 'edit_post', $post->ID ) ) 155 241 { 156 if ( in_array( (string) $post->ID, $this->network->collisions, true ) ) 242 $collisions = $this->network->collisions; 243 244 if ( array_intersect( get_comments( 'fields=ids&post_id=' . $post->ID ), $collisions['comments'] ) !== array() || in_array( (string) $post->ID, $collisions['posts'], true ) || array_intersect( wp_get_object_terms( $post->ID, array_keys( $GLOBALS['wp_taxonomies'] ), 'fields=tt_ids' ), $collisions['term_taxonomy'] ) !== array() || array_intersect( wp_get_object_terms( $post->ID, array_keys( $GLOBALS['wp_taxonomies'] ), 'fields=ids' ), $collisions['terms'] ) !== array() ) 157 245 { 158 246 $actions['republish'] = '<i style="color: #888;">' . __( 'Can't Republish', 'supernetwork' ) . '</i>'; -
wp-super-network/trunk/wp-super-network.php
r2671848 r2864100 4 4 * Plugin URI: 5 5 * Description: Share content between sites and create offspring networks. 6 * Version: 1. 1.06 * Version: 1.2.0 7 7 * Requires at least: 5.0 8 * Requires PHP: 5.68 * Requires PHP: 7.2 9 9 * Author: Ask Carlo 10 10 * Author URI: https://askcarlo.com
Note: See TracChangeset
for help on using the changeset viewer.