├── LICENSE ├── asset └── dist │ ├── css │ └── styles.css │ └── js │ ├── script-113.js │ └── script-113.min.js ├── class ├── cli │ └── wpmdbpro-multisite-tools-cli.php └── wpmdbpro-multisite-tools.php ├── languages └── wp-migrate-db-pro-multisite-tools-en.pot ├── template └── migrate.php ├── version.php └── wp-migrate-db-pro-multisite-tools.php /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /asset/dist/css/styles.css: -------------------------------------------------------------------------------- 1 | .new-prefix-field{margin-top:10px}.new-prefix-field input{margin-left:10px} 2 | -------------------------------------------------------------------------------- /asset/dist/js/script-113.js: -------------------------------------------------------------------------------- 1 | var wpmdb = wpmdb || {}; 2 | wpmdb.mst = { 3 | remote_mst_unavailable: false 4 | }; 5 | 6 | (function( $, wpmdb ) { 7 | var $mst_options = $( '.mst-options' ); 8 | var $mst_select_subsite = $( '#mst-select-subsite' ); 9 | var $mst_options_content = $( '.mst-options .expandable-content' ); 10 | var $mst_selected_subsite = $( '#mst-selected-subsite' ); 11 | var $mst_new_prefix_field = $( '.new-prefix-field' ); 12 | var $mst_new_prefix = $( '#new-prefix' ); 13 | var $mst_new_prefix_hidden = $( '#new-prefix-hidden' ); 14 | var $mst_new_prefix_readonly = $( '#new-prefix-readonly' ); 15 | var $mst_unavailable = $( '.mst-unavailable' ); 16 | var $mst_different_plugin_version_notice = $( '.mst-different-plugin-version-notice' ); 17 | var $mst_different_prefix_notice = $( '.mst-different-prefix-notice' ); 18 | 19 | var finished_loading = false; 20 | var original_local_url = null; 21 | var reverse_replace = false; 22 | var table_prefix = $( '.table-select-wrap .table-prefix' ).text(); 23 | 24 | function doing_mst_select_subsite() { 25 | return '1' === $mst_select_subsite.attr( 'data-available' ) && $mst_select_subsite.is( ':checked' ) ? true : false; 26 | } 27 | 28 | function disable_mst_options() { 29 | $.wpmdb.do_action( 'wpmdb_lock_replace_url', false ); 30 | $.wpmdb.do_action( 'wpmdb_enable_table_migration_options' ); 31 | $mst_select_subsite.attr( 'data-available', '0' ); 32 | $mst_select_subsite.prop( 'checked', false ); 33 | $mst_select_subsite.attr( 'disabled', 'disabled' ); 34 | $( '.mst' ).addClass( 'disabled' ); 35 | $mst_options_content.hide(); 36 | } 37 | 38 | function enable_mst_options() { 39 | $mst_select_subsite.attr( 'data-available', '1' ); 40 | $mst_select_subsite.removeAttr( 'disabled' ); 41 | $( '.mst' ).removeClass( 'disabled' ); 42 | } 43 | 44 | function hide_show_options( unavailable ) { 45 | 46 | // MST options can only be used from a multisite install at present. 47 | // TODO: Remove this test if MST is to be usable from a single site install. 48 | if ( 'false' === wpmdb_data.site_details.is_multisite ) { 49 | disable_mst_options(); 50 | $mst_options.hide(); 51 | return; 52 | } 53 | 54 | // For Pull/Push remote should be a single site install. 55 | // TODO: Remove this test if MST is to manage subsite to subsite. 56 | if ( 'savefile' !== wpmdb_migration_type() && 57 | 'undefined' !== typeof wpmdb.mst.remote_connection_data && 58 | 'true' === wpmdb.mst.remote_connection_data.site_details.is_multisite ) { 59 | disable_mst_options(); 60 | $mst_options.hide(); 61 | return; 62 | } 63 | 64 | // For Pull/Push remote should have same base prefix. 65 | // TODO: Allow migrating to different base prefix. 66 | if ( 'savefile' !== wpmdb_migration_type() && 67 | false === unavailable && 68 | 'undefined' !== typeof wpmdb.mst.remote_connection_data && 69 | 'undefined' !== typeof wpmdb.mst.remote_connection_data.site_details && 70 | wpmdb_data.site_details.prefix !== wpmdb.mst.remote_connection_data.site_details.prefix ) { 71 | disable_mst_options(); 72 | maybe_update_local_url_for_subsite( false ); 73 | $( '.mst-remote-location' ).html( wpmdb.mst.remote_connection_data.url ); 74 | $( '.mst-remote-prefix' ).html( wpmdb.mst.remote_connection_data.site_details.prefix ); 75 | $mst_different_prefix_notice.show(); 76 | return; 77 | } 78 | $mst_different_prefix_notice.hide(); 79 | 80 | // For Pull/Push remote also needs MST. 81 | if ( unavailable && 'true' === wpmdb_data.site_details.is_multisite && 'savefile' !== wpmdb_migration_type() ) { 82 | disable_mst_options(); 83 | maybe_update_local_url_for_subsite( false ); 84 | $mst_unavailable.show(); 85 | return; 86 | } 87 | $mst_unavailable.hide(); 88 | 89 | if ( 'savefile' !== wpmdb_migration_type() && 90 | 'undefined' !== typeof wpmdb.mst.remote_connection_data && 91 | wpmdb_data.mst_version !== wpmdb.mst.remote_connection_data.mst_version ) { 92 | disable_mst_options(); 93 | maybe_update_local_url_for_subsite( false ); 94 | $( '.mst-remote-location' ).html( wpmdb.mst.remote_connection_data.url ); 95 | $( '.mst-remote-version' ).html( wpmdb.mst.remote_connection_data.mst_version ); 96 | $mst_different_plugin_version_notice.show(); 97 | return; 98 | } 99 | $mst_different_plugin_version_notice.hide(); 100 | 101 | enable_mst_options(); 102 | if ( doing_mst_select_subsite() ) { 103 | $mst_options_content.show(); 104 | selected_subsite_changed(); 105 | } else { 106 | $mst_options_content.hide(); 107 | } 108 | 109 | maybe_lock_replace_url(); 110 | 111 | $mst_options.show(); 112 | } 113 | 114 | function maybe_lock_replace_url() { 115 | if ( doing_mst_select_subsite() && 'savefile' !== wpmdb_migration_type() ) { 116 | $.wpmdb.do_action( 'wpmdb_lock_replace_url', true ); 117 | } else { 118 | $.wpmdb.do_action( 'wpmdb_lock_replace_url', false ); 119 | } 120 | } 121 | 122 | function hide_show_new_prefix_field() { 123 | if ( '' === $mst_selected_subsite.val() ) { 124 | $mst_new_prefix_field.hide(); 125 | return; 126 | } 127 | 128 | // If not an Export, we know what the new table prefix should be. 129 | if ( 'savefile' !== wpmdb_migration_type() ) { 130 | $mst_new_prefix.hide(); 131 | $mst_new_prefix_readonly.show(); 132 | $mst_new_prefix_field.children( 'label' ).addClass( 'disabled' ); 133 | 134 | var new_prefix = table_prefix; 135 | var selected_subsite = get_selected_subsite(); 136 | 137 | if ( 'pull' === wpmdb_migration_type() ) { 138 | if ( undefined !== selected_subsite.blog_id && 1 < selected_subsite.blog_id ) { 139 | new_prefix = new_prefix + selected_subsite.blog_id + '_'; 140 | } 141 | } else { 142 | 143 | // TODO: Determine new table prefix if target has different base or is not single site install. 144 | } 145 | $mst_new_prefix.val( new_prefix ); 146 | $mst_new_prefix_hidden.val( new_prefix ); 147 | $mst_new_prefix_readonly.text( new_prefix ); 148 | } else { 149 | $mst_new_prefix.show(); 150 | $mst_new_prefix_readonly.hide(); 151 | $mst_new_prefix_field.children( 'label' ).removeClass( 'disabled' ); 152 | } 153 | $mst_new_prefix_field.show(); 154 | } 155 | 156 | function select_subsite_changed() { 157 | var args = doing_mst_select_subsite(); 158 | 159 | maybe_lock_replace_url(); 160 | 161 | $.wpmdb.do_action( 'wpmdbmst_select_subsite_changed', args ); 162 | } 163 | 164 | function get_selected_subsite() { 165 | var details = {}; 166 | if ( doing_mst_select_subsite() ) { 167 | var blog_id = $mst_selected_subsite.find( 'option:selected' ).val(); 168 | 169 | if ( '' === blog_id ) { 170 | details = false; 171 | } else { 172 | details.blog_id = blog_id; 173 | details.domain_and_path = $mst_selected_subsite.find( 'option:selected' ).text(); 174 | } 175 | } 176 | 177 | return details; 178 | } 179 | 180 | function selected_subsite_changed() { 181 | var selected_subsite = get_selected_subsite(); 182 | $.wpmdb.do_action( 'wpmdbmst_selected_subsite_changed', selected_subsite ); 183 | } 184 | 185 | var subsite_for_tables = false; 186 | 187 | function update_table_selects( selected_subsite ) { 188 | 189 | // Force table select lists to be refreshed (and filtered again). 190 | $.wpmdb.do_action( 'wpmdb_refresh_table_selects' ); 191 | 192 | if ( 'pull' === wpmdb_migration_type() ) { 193 | $.wpmdb.do_action( 'wpmdb_update_pull_table_select' ); 194 | } else { 195 | $.wpmdb.do_action( 'wpmdb_update_push_table_select' ); 196 | } 197 | 198 | // We may need to enable or disable the ability to select the "Migrate all tables with prefix ..." option. 199 | if ( doing_mst_select_subsite() ) { 200 | $.wpmdb.do_action( 'wpmdb_disable_table_migration_options' ); 201 | 202 | // When switching subsites select all the tables unless still loading saved profile. 203 | if ( finished_loading && 204 | ( 205 | false === subsite_for_tables || 206 | ( undefined !== subsite_for_tables.blog_id && undefined !== selected_subsite.blog_id && subsite_for_tables.blog_id !== selected_subsite.blog_id ) 207 | ) 208 | ) { 209 | $.wpmdb.do_action( 'wpmdb_select_all_tables' ); 210 | } 211 | } else { 212 | $.wpmdb.do_action( 'wpmdb_enable_table_migration_options' ); 213 | } 214 | 215 | subsite_for_tables = selected_subsite; 216 | } 217 | 218 | function select_subsite_tables_on_change_action( data ) { 219 | if ( undefined !== wpmdb.mst.remote_connection_data && 220 | 'true' !== wpmdb.mst.remote_connection_data.site_details.is_multisite && 221 | doing_mst_select_subsite() && 222 | data.last_migration_type !== data.migration_type 223 | ) { 224 | 225 | // Timeout required otherwise wpmdb_update_push/pull_table_select action runs after this 226 | setTimeout( function() { 227 | $.wpmdb.do_action( 'wpmdb_select_all_tables' ); 228 | } ); 229 | } 230 | } 231 | 232 | function maybe_update_local_url_for_subsite( selected_subsite ) { 233 | var new_local_url = original_local_url; 234 | 235 | if ( undefined === selected_subsite ) { 236 | return; 237 | } else if ( undefined !== selected_subsite.domain_and_path ) { 238 | new_local_url = '//' + selected_subsite.domain_and_path; 239 | } 240 | 241 | var replace_right = false; 242 | if ( 'pull' === wpmdb_migration_type() ) { 243 | replace_right = true; 244 | } 245 | 246 | if ( reverse_replace ) { 247 | replace_right = !replace_right; 248 | } 249 | 250 | if ( replace_right ) { 251 | $( '.replace-row.pin .replace-right-col input[type="text"]' ).val( new_local_url ); 252 | } else { 253 | $( '.replace-row.pin .old-replace-col input[type="text"]' ).val( new_local_url ); 254 | } 255 | } 256 | 257 | function is_subsite_table( table_prefix, table_name ) { 258 | var selected_subsite = get_selected_subsite(); 259 | 260 | if ( undefined !== selected_subsite.blog_id ) { 261 | if ( 1 < selected_subsite.blog_id ) { 262 | table_prefix = table_prefix + selected_subsite.blog_id + '_'; 263 | var pos = table_name.toLowerCase().indexOf( table_prefix.toLowerCase() ); 264 | 265 | if ( 0 === pos ) { 266 | return true; 267 | } 268 | } else { 269 | var escaped_table_name = wpmdb.preg_quote( table_name ); 270 | var regex = new RegExp( table_prefix + '([0-9]+)_', 'i' ); 271 | var results = regex.exec( escaped_table_name ); 272 | return null == results; // If null is returned, there was no match which is good in this case. 273 | } 274 | } 275 | 276 | return false; 277 | } 278 | 279 | function filter_table_for_subsite( exclude, table_name ) { 280 | if ( 'false' === wpmdb_data.site_details.is_multisite ) { 281 | return exclude; 282 | } 283 | 284 | if ( doing_mst_select_subsite() ) { 285 | 286 | // If there is no subsite selected then we should exclude all tables. 287 | if ( false === get_selected_subsite() ) { 288 | return true; 289 | } 290 | 291 | // If table does not have correct base table prefix for site then exclude from subsite export. 292 | if ( table_name.substr( 0, table_prefix.length ) !== table_prefix ) { 293 | return true; 294 | } 295 | 296 | if ( 1 === wpmdb.subsite_for_table( table_prefix, table_name ) ) { 297 | 298 | // wp_users and wp_usermeta are relevant to all sites, shortcut out. 299 | if ( wpmdb.table_is( table_prefix, 'users', table_name ) || wpmdb.table_is( table_prefix, 'usermeta', table_name ) ) { 300 | return exclude; 301 | } 302 | 303 | // Following tables are Multisite setup tables and can be excluded from migration. 304 | // We'll handle getting any data we need from these tables elsewhere. 305 | var ms_tables = [ 'blog_versions', 'blogs', 'registration_log', 'signups', 'site', 'sitemeta' ]; 306 | 307 | $.each( ms_tables, function( index, ms_table ) { 308 | if ( wpmdb.table_is( table_prefix, ms_table, table_name ) ) { 309 | exclude = true; 310 | } 311 | } ); 312 | } 313 | 314 | // If pulling from a single site install, all properly prefixed tables are relevant. 315 | if ( 'pull' === wpmdb_migration_type() && 316 | 'undefined' !== typeof wpmdb.mst.remote_connection_data && 317 | 'true' !== wpmdb.mst.remote_connection_data.site_details.is_multisite ) { 318 | return exclude; 319 | } 320 | 321 | if ( false === is_subsite_table( table_prefix, table_name ) ) { 322 | exclude = true; 323 | } 324 | } 325 | 326 | return exclude; 327 | } 328 | 329 | function validate_new_prefix( new_prefix ) { 330 | var escaped_new_prefix = wpmdb.preg_quote( new_prefix ); 331 | 332 | var regex = new RegExp( '[^a-z0-9_]', 'i' ); 333 | var results = regex.exec( escaped_new_prefix ); 334 | return null == results; // If null is returned there was no match, which is good in this case. 335 | } 336 | 337 | function filter_migration_profile_ready( value, args ) { 338 | if ( 'false' === wpmdb_data.site_details.is_multisite || false === doing_mst_select_subsite() ) { 339 | return value; 340 | } 341 | 342 | var new_prefix = $mst_new_prefix.val(); 343 | 344 | if ( false === get_selected_subsite() ) { 345 | alert( wpmdbmst_strings.please_select_a_subsite ); 346 | value = false; 347 | } else if ( 0 === new_prefix.trim().length ) { 348 | alert( wpmdbmst_strings.please_enter_a_prefix ); 349 | value = false; 350 | } else if ( false === validate_new_prefix( new_prefix ) ) { 351 | alert( wpmdbmst_strings.new_prefix_contents ); 352 | value = false; 353 | } 354 | 355 | return value; 356 | } 357 | 358 | function filter_backup_selected_tables( selected_tables ) { 359 | if ( doing_mst_select_subsite() ) { 360 | var selected_subsite = get_selected_subsite(); 361 | 362 | // If dealing with non-primary subsite tables and a single site install, we may need to adjust table names to be backed up. 363 | if ( undefined !== selected_subsite.blog_id && 1 < selected_subsite.blog_id && 364 | ( 'false' === wpmdb_data.site_details.is_multisite || 'true' !== wpmdb.mst.remote_connection_data.site_details.is_multisite ) ) { 365 | $.each( selected_tables, function( index, table_name ) { 366 | if ( false === wpmdb.table_is( table_prefix, 'users', table_name ) && false === wpmdb.table_is( table_prefix, 'usermeta', table_name ) ) { 367 | 368 | // Table to backup needs subsite prefix? 369 | if ( false === is_subsite_table( table_prefix, table_name ) && ( 370 | ( 'pull' === wpmdb_migration_type() && 'true' === wpmdb_data.site_details.is_multisite ) || 371 | ( 'push' === wpmdb_migration_type() && 'true' === wpmdb.mst.remote_connection_data.site_details.is_multisite ) 372 | ) ) { 373 | selected_tables[ index ] = $mst_new_prefix.val() + table_name.substr( table_prefix.length ); 374 | } 375 | 376 | // Table to backup needs subsite prefix removed? 377 | if ( true === is_subsite_table( table_prefix, table_name ) && ( 378 | ( 'pull' === wpmdb_migration_type() && 'false' === wpmdb_data.site_details.is_multisite ) || 379 | ( 'push' === wpmdb_migration_type() && 'true' !== wpmdb.mst.remote_connection_data.site_details.is_multisite ) 380 | ) ) { 381 | selected_tables[ index ] = $mst_new_prefix.val() + table_name.substr( ( table_prefix + selected_subsite.blog_id + '_' ).length ); 382 | } 383 | } 384 | } ); 385 | } 386 | } 387 | 388 | return selected_tables; 389 | } 390 | 391 | function filter_mf_enable_select_subsites( enable ) { 392 | if ( false !== enable && doing_mst_select_subsite() ) { 393 | enable = false; 394 | } 395 | 396 | return enable; 397 | } 398 | 399 | function update_remote_connection_data( connection_data ) { 400 | wpmdb.mst.remote_connection_data = connection_data; 401 | wpmdb.mst.remote_mst_unavailable = ( 'undefined' === typeof connection_data.mst_available ); 402 | } 403 | 404 | // IMPORTANT: This action fires before find/replace columns are swapped for pull/push. 405 | $.wpmdb.add_action( 'move_connection_info_box', function( args ) { 406 | table_prefix = $( '.table-select-wrap .table-prefix' ).text(); 407 | if ( null === original_local_url ) { 408 | original_local_url = $.wpmdb.apply_filters( 'wpmdb_base_old_url' ); 409 | } 410 | 411 | if ( undefined !== args.migration_type && undefined !== args.last_migration_type ) { 412 | if ( args.migration_type !== args.last_migration_type && ( 'pull' === args.migration_type || 'pull' === args.last_migration_type ) ) { 413 | reverse_replace = true; 414 | } 415 | } 416 | wpmdb_toggle_migration_action_text(); 417 | hide_show_options( wpmdb.mst.remote_mst_unavailable ); 418 | $.wpmdb.do_action( 'wpmdb_refresh_table_selects' ); 419 | if ( reverse_replace ) { 420 | reverse_replace = false; 421 | } 422 | } ); 423 | 424 | $.wpmdb.add_action( 'verify_connection_to_remote_site', function( connection_data ) { 425 | update_remote_connection_data( connection_data ); 426 | hide_show_options( wpmdb.mst.remote_mst_unavailable ); 427 | } ); 428 | 429 | $.wpmdb.add_action( 'wpmdbmst_select_subsite_changed', selected_subsite_changed ); 430 | $.wpmdb.add_action( 'wpmdbmst_selected_subsite_changed', update_table_selects ); 431 | $.wpmdb.add_action( 'wpmdbmst_selected_subsite_changed', maybe_update_local_url_for_subsite ); 432 | $.wpmdb.add_action( 'wpmdbmst_selected_subsite_changed', hide_show_new_prefix_field ); 433 | $.wpmdb.add_action( 'move_connection_info_box', select_subsite_tables_on_change_action ); 434 | 435 | $.wpmdb.add_action( 'wpmdb_connection_data_updated', update_remote_connection_data ); 436 | 437 | $.wpmdb.add_filter( 'wpmdb_exclude_table', filter_table_for_subsite ); 438 | $.wpmdb.add_filter( 'wpmdb_migration_profile_ready', filter_migration_profile_ready ); 439 | $.wpmdb.add_filter( 'wpmdb_backup_selected_tables', filter_backup_selected_tables ); 440 | 441 | $.wpmdb.add_filter( 'wpmdbmf_enable_select_subsites', filter_mf_enable_select_subsites ); 442 | 443 | $( document ).ready( function() { 444 | $( 'body' ).on( 'change', '#mst-select-subsite', function( e ) { 445 | select_subsite_changed(); 446 | } ); 447 | 448 | $( 'body' ).on( 'change', '#mst-selected-subsite', function( e ) { 449 | selected_subsite_changed(); 450 | } ); 451 | 452 | hide_show_options( wpmdb.mst.remote_mst_unavailable ); 453 | finished_loading = true; 454 | } ); 455 | 456 | })( jQuery, wpmdb ); 457 | -------------------------------------------------------------------------------- /asset/dist/js/script-113.min.js: -------------------------------------------------------------------------------- 1 | var wpmdb=wpmdb||{};wpmdb.mst={remote_mst_unavailable:!1},function(a,b){function c(){return"1"===w.attr("data-available")&&w.is(":checked")?!0:!1}function d(){a.wpmdb.do_action("wpmdb_lock_replace_url",!1),a.wpmdb.do_action("wpmdb_enable_table_migration_options"),w.attr("data-available","0"),w.prop("checked",!1),w.attr("disabled","disabled"),a(".mst").addClass("disabled"),x.hide()}function e(){w.attr("data-available","1"),w.removeAttr("disabled"),a(".mst").removeClass("disabled")}function f(f){return"false"===wpmdb_data.site_details.is_multisite?(d(),void v.hide()):"savefile"!==wpmdb_migration_type()&&"undefined"!=typeof b.mst.remote_connection_data&&"true"===b.mst.remote_connection_data.site_details.is_multisite?(d(),void v.hide()):"savefile"!==wpmdb_migration_type()&&!1===f&&"undefined"!=typeof b.mst.remote_connection_data&&"undefined"!=typeof b.mst.remote_connection_data.site_details&&wpmdb_data.site_details.prefix!==b.mst.remote_connection_data.site_details.prefix?(d(),n(!1),a(".mst-remote-location").html(b.mst.remote_connection_data.url),a(".mst-remote-prefix").html(b.mst.remote_connection_data.site_details.prefix),void F.show()):(F.hide(),f&&"true"===wpmdb_data.site_details.is_multisite&&"savefile"!==wpmdb_migration_type()?(d(),n(!1),void D.show()):(D.hide(),"savefile"!==wpmdb_migration_type()&&"undefined"!=typeof b.mst.remote_connection_data&&wpmdb_data.mst_version!==b.mst.remote_connection_data.mst_version?(d(),n(!1),a(".mst-remote-location").html(b.mst.remote_connection_data.url),a(".mst-remote-version").html(b.mst.remote_connection_data.mst_version),void E.show()):(E.hide(),e(),c()?(x.show(),k()):x.hide(),g(),void v.show())))}function g(){c()&&"savefile"!==wpmdb_migration_type()?a.wpmdb.do_action("wpmdb_lock_replace_url",!0):a.wpmdb.do_action("wpmdb_lock_replace_url",!1)}function h(){if(""===y.val())return void z.hide();if("savefile"!==wpmdb_migration_type()){A.hide(),C.show(),z.children("label").addClass("disabled");var a=J,b=j();"pull"===wpmdb_migration_type()&&void 0!==b.blog_id&&1 45 | $mst_select_subsite = false; 46 | $mst_selected_subsite = 0; 47 | if ( isset( $assoc_args['subsite'] ) ) { 48 | if ( ! is_multisite() ) { 49 | return $wpmdbpro_cli->cli_error( __( 'The installation must be a Multisite network to make use of the subsite option', 'wp-migrate-db-pro-multisite-tools' ) ); 50 | } 51 | if ( empty( $assoc_args['subsite'] ) ) { 52 | return $wpmdbpro_cli->cli_error( __( 'A valid Blog ID or Subsite URL must be supplied to make use of the subsite option', 'wp-migrate-db-pro-multisite-tools' ) ); 53 | } 54 | $mst_selected_subsite = $this->get_subsite_id( $assoc_args['subsite'] ); 55 | 56 | if ( false === $mst_selected_subsite ) { 57 | return $wpmdbpro_cli->cli_error( __( 'A valid Blog ID or Subsite URL must be supplied to make use of the subsite option', 'wp-migrate-db-pro-multisite-tools' ) ); 58 | } 59 | 60 | $mst_select_subsite = true; 61 | } 62 | 63 | // --prefix= 64 | global $wpdb; 65 | $new_prefix = $wpdb->base_prefix; 66 | if ( isset( $assoc_args['prefix'] ) ) { 67 | if ( false === $mst_select_subsite ) { 68 | return $wpmdbpro_cli->cli_error( __( 'A new table name prefix may only be specified for subsite exports.', 'wp-migrate-db-pro-multisite-tools' ) ); 69 | } 70 | if ( empty( $assoc_args['prefix'] ) ) { 71 | return $wpmdbpro_cli->cli_error( __( 'A valid prefix must be supplied to make use of the prefix option', 'wp-migrate-db-pro-multisite-tools' ) ); 72 | } 73 | $new_prefix = trim( $assoc_args['prefix'] ); 74 | 75 | if ( sanitize_key( $new_prefix ) !== $new_prefix ) { 76 | return $wpmdbpro_cli->cli_error( $this->get_string( 'new_prefix_contents' ) ); 77 | } 78 | } elseif ( 1 < $mst_selected_subsite && 'pull' === $profile['action'] ) { 79 | $new_prefix .= $mst_selected_subsite . '_'; 80 | } 81 | 82 | // Disable Media Files Select Subsites if using Subsite Migration. 83 | if ( $mst_select_subsite && ! empty( $profile['mf_select_subsites'] ) && ! empty( $profile['mf_selected_subsites'] ) ) { 84 | unset( $profile['mf_select_subsites'], $profile['mf_selected_subsites'] ); 85 | } 86 | 87 | $filtered_profile = compact( 88 | 'mst_select_subsite', 89 | 'mst_selected_subsite', 90 | 'new_prefix' 91 | ); 92 | 93 | return array_merge( $profile, $filtered_profile ); 94 | } 95 | 96 | /** 97 | * Ensure CLI has appropriate default find and replace values when doing MST. 98 | * 99 | * @param array $profile 100 | * @param array $post_data 101 | * 102 | * @return array 103 | */ 104 | public function filter_cli_default_find_and_replace( $profile, $post_data ) { 105 | if ( is_wp_error( $profile ) ) { 106 | return $profile; 107 | } 108 | 109 | if ( empty( $profile['mst_select_subsite'] ) || empty( $profile['mst_selected_subsite'] ) || 1 >= $profile['mst_selected_subsite'] ) { 110 | return $profile; 111 | } 112 | 113 | $source = ( 'pull' === $post_data['intent'] ) ? 'remote' : 'local'; 114 | $target = ( 'pull' === $post_data['intent'] ) ? 'local' : 'remote'; 115 | 116 | if ( 'true' === $post_data['site_details'][ $source ]['is_multisite'] && ! empty( $post_data['site_details'][ $source ]['subsites_info'][ $profile['mst_selected_subsite'] ]['site_url'] ) ) { 117 | $profile['replace_old'][1] = '//' . untrailingslashit( $this->scheme_less_url( $post_data['site_details'][ $source ]['subsites_info'][ $profile['mst_selected_subsite'] ]['site_url'] ) ); 118 | } 119 | 120 | if ( 'true' === $post_data['site_details'][ $target ]['is_multisite'] && ! empty( $post_data['site_details'][ $target ]['subsites_info'][ $profile['mst_selected_subsite'] ]['site_url'] ) ) { 121 | $profile['replace_new'][1] = '//' . untrailingslashit( $this->scheme_less_url( $post_data['site_details'][ $target ]['subsites_info'][ $profile['mst_selected_subsite'] ]['site_url'] ) ); 122 | } 123 | 124 | return $profile; 125 | } 126 | } -------------------------------------------------------------------------------- /class/wpmdbpro-multisite-tools.php: -------------------------------------------------------------------------------- 1 | plugin_slug = 'wp-migrate-db-pro-multisite-tools'; 13 | $this->plugin_version = $GLOBALS['wpmdb_meta']['wp-migrate-db-pro-multisite-tools']['version']; 14 | if ( ! $this->meets_version_requirements( '1.6.1' ) ) { 15 | return; 16 | } 17 | 18 | $this->accepted_fields = array( 19 | 'multisite_subsite_export', // TODO: Remove backwards compatibility for CLI once Core/MST/CLI dependencies updated. 20 | 'select_subsite', // TODO: Remove backwards compatibility for CLI once Core/MST/CLI dependencies updated. 21 | 'mst_select_subsite', 22 | 'mst_selected_subsite', 23 | 'new_prefix', 24 | 'keep_active_plugins', 25 | ); 26 | 27 | add_action( 'wpmdb_before_migration_options', array( $this, 'migration_form_controls' ) ); 28 | add_action( 'wpmdb_load_assets', array( $this, 'load_assets' ) ); 29 | add_action( 'wpmdb_diagnostic_info', array( $this, 'diagnostic_info' ) ); 30 | add_filter( 'wpmdb_accepted_profile_fields', array( $this, 'accepted_profile_fields' ) ); 31 | add_filter( 'wpmdb_establish_remote_connection_data', array( $this, 'establish_remote_connection_data' ) ); 32 | add_filter( 'wpmdb_data', array( $this, 'js_variables' ) ); 33 | 34 | add_filter( 'wpmdb_exclude_table', array( $this, 'filter_table_for_subsite' ), 10, 2 ); 35 | add_filter( 'wpmdb_tables', array( $this, 'filter_tables_for_subsite' ), 10, 2 ); 36 | add_filter( 'wpmdb_table_sizes', array( $this, 'filter_table_sizes_for_subsite' ), 10, 2 ); 37 | add_filter( 'wpmdb_target_table_name', array( $this, 'filter_target_table_name' ), 10, 4 ); 38 | add_filter( 'wpmdb_table_row', array( $this, 'filter_table_row' ), 10, 4 ); 39 | add_filter( 'wpmdb_find_and_replace', array( $this, 'filter_find_and_replace' ), 10, 3 ); 40 | add_filter( 'wpmdb_finalize_target_table_name', array( $this, 'filter_finalize_target_table_name' ), 10, 3 ); 41 | add_filter( 'wpmdb_preserved_options', array( $this, 'filter_preserved_options' ), 10, 2 ); 42 | add_filter( 'wpmdb_preserved_options_data', array( $this, 'filter_preserved_options_data' ), 10, 2 ); 43 | add_filter( 'wpmdb_get_alter_queries', array( $this, 'filter_get_alter_queries' ) ); 44 | 45 | global $wpmdbpro; 46 | $this->wpmdbpro = $wpmdbpro; 47 | 48 | if ( class_exists( 'WPMDBPro_Media_Files' ) ) { 49 | add_filter( 'wpmdbmf_include_subsite', array( $this, 'include_subsite' ), 10, 3 ); 50 | add_filter( 'wpmdbmf_destination_file_path', array( $this, 'filter_mf_destination_file_path' ), 10, 3 ); 51 | add_filter( 'wpmdbmf_file_not_on_local', array( $this, 'filter_mf_file_not_on_local' ), 10, 3 ); 52 | add_filter( 'wpmdbmf_get_remote_attachment_batch_response', array( $this, 'filter_mf_get_remote_attachment_batch_response', ), 10, 3 ); 53 | add_filter( 'wpmdbmf_exclude_local_media_file_from_removal', array( $this, 'filter_mf_exclude_local_media_file_from_removal', ), 10, 4 ); 54 | add_filter( 'wpmdbmf_file_to_download', array( $this, 'filter_mf_file_to_download', ), 10, 3 ); 55 | } 56 | } 57 | 58 | /** 59 | * Does the given user need to be migrated? 60 | * 61 | * @param int $user_id 62 | * @param int $blog_id Optional. 63 | * 64 | * @return bool 65 | */ 66 | private function is_user_required_for_blog( $user_id, $blog_id = 0 ) { 67 | static $users = array(); 68 | 69 | if ( empty( $user_id ) ) { 70 | $user_id = 0; 71 | } 72 | 73 | if ( empty( $blog_id ) ) { 74 | $blog_id = 0; 75 | } 76 | 77 | if ( isset( $users[ $blog_id ][ $user_id ] ) ) { 78 | return $users[ $blog_id ][ $user_id ]; 79 | } 80 | 81 | if ( ! is_multisite() ) { 82 | $users[ $blog_id ][ $user_id ] = true; 83 | 84 | return $users[ $blog_id ][ $user_id ]; 85 | } 86 | 87 | $subsites = $this->subsites_list(); 88 | 89 | if ( empty( $subsites ) || ! array_key_exists( $blog_id, $subsites ) ) { 90 | $users[ $blog_id ][ $user_id ] = false; 91 | 92 | return $users[ $blog_id ][ $user_id ]; 93 | } 94 | 95 | if ( is_user_member_of_blog( $user_id, $blog_id ) ) { 96 | $users[ $blog_id ][ $user_id ] = true; 97 | 98 | return $users[ $blog_id ][ $user_id ]; 99 | } 100 | 101 | // If the user has any posts that are going to be migrated, we need the user regardless of whether they still have access. 102 | switch_to_blog( $blog_id ); 103 | $user_posts = count_user_posts( $user_id ); 104 | restore_current_blog(); 105 | 106 | if ( 0 < $user_posts ) { 107 | $users[ $blog_id ][ $user_id ] = true; 108 | 109 | return $users[ $blog_id ][ $user_id ]; 110 | } 111 | 112 | // If here, user not required. 113 | $users[ $blog_id ][ $user_id ] = false; 114 | 115 | return $users[ $blog_id ][ $user_id ]; 116 | } 117 | 118 | /** 119 | * Return subsite id if subsite selected. 120 | * 121 | * @param WPMDB_Base $plugin_instance 122 | * 123 | * @return int Will return 0 if not doing MST migration. 124 | * 125 | * Will return 0 if not doing MST migration. 126 | */ 127 | public function selected_subsite( &$plugin_instance = null ) { 128 | $blog_id = 0; 129 | 130 | if ( empty( $plugin_instance ) ) { 131 | $plugin_instance = &$this->wpmdbpro; 132 | } 133 | 134 | $this->state_data = $plugin_instance->set_post_data(); 135 | 136 | if ( ! empty( $this->state_data['form_data'] ) ) { 137 | $this->form_data = $this->parse_migration_form_data( $this->state_data['form_data'] ); 138 | 139 | $select_subsite = $this->profile_value( 'mst_select_subsite' ); 140 | $selected_subsite = $this->profile_value( 'mst_selected_subsite' ); 141 | 142 | // TODO: Remove backwards compatibility for CLI once Core/MST/CLI dependencies updated. 143 | if ( empty( $select_subsite ) && empty( $selected_subsite ) ) { 144 | $select_subsite = $this->profile_value( 'multisite_subsite_export' ); 145 | $selected_subsite = $this->profile_value( 'select_subsite' ); 146 | } 147 | 148 | // During a migration, this is where the subsite's id will be derived. 149 | if ( empty( $blog_id ) && 150 | ! empty( $select_subsite ) && 151 | ! empty( $selected_subsite ) && 152 | is_numeric( $selected_subsite ) 153 | ) { 154 | $blog_id = $selected_subsite; 155 | } 156 | } 157 | 158 | // When loading a saved migration profile, this is where the subsite's id will be derived. 159 | global $loaded_profile; 160 | if ( empty( $blog_id ) && 161 | ! empty( $loaded_profile['mst_select_subsite'] ) && 162 | ! empty( $loaded_profile['mst_selected_subsite'] ) && 163 | is_numeric( $loaded_profile['mst_selected_subsite'] ) 164 | ) { 165 | $blog_id = $loaded_profile['mst_selected_subsite']; 166 | } 167 | 168 | // If on multisite we can check that selected blog exists as all scenarios would require it. 169 | if ( 1 < $blog_id && is_multisite() && ! $this->subsite_exists( $blog_id ) ) { 170 | $blog_id = 0; 171 | } 172 | 173 | return $blog_id; 174 | } 175 | 176 | /** 177 | * Adds the multisite tools settings to the migration setting page in core. 178 | */ 179 | public function migration_form_controls() { 180 | $this->template( 'migrate' ); 181 | } 182 | 183 | /** 184 | * Whitelist multisite tools setting fields for use in AJAX save in core 185 | * 186 | * @param array $profile_fields 187 | * 188 | * @return array 189 | */ 190 | public function accepted_profile_fields( $profile_fields ) { 191 | return array_merge( $profile_fields, $this->accepted_fields ); 192 | } 193 | 194 | /** 195 | * Check the remote site has the multisite tools addon setup 196 | * 197 | * @param array $data Connection data 198 | * 199 | * @return array Updated connection data 200 | */ 201 | public function establish_remote_connection_data( $data ) { 202 | $data['mst_available'] = '1'; 203 | $data['mst_version'] = $this->plugin_version; 204 | 205 | return $data; 206 | } 207 | 208 | /** 209 | * Add multisite tools related javascript variables to the page 210 | * 211 | * @param array $data 212 | * 213 | * @return array 214 | */ 215 | public function js_variables( $data ) { 216 | $data['mst_version'] = $this->plugin_version; 217 | 218 | return $data; 219 | } 220 | 221 | /** 222 | * Get translated strings for javascript and other functions. 223 | * 224 | * @return array 225 | */ 226 | public function get_strings() { 227 | static $strings; 228 | 229 | if ( ! empty( $strings ) ) { 230 | return $strings; 231 | } 232 | 233 | $strings = array( 234 | 'migration_failed' => __( 'Migration failed', 'wp-migrate-db-pro-multisite-tools' ), 235 | 'please_select_a_subsite' => __( 'Please select a subsite.', 'wp-migrate-db-pro-multisite-tools' ), 236 | 'please_enter_a_prefix' => __( 'Please enter a new table prefix.', 'wp-migrate-db-pro-multisite-tools' ), 237 | 'new_prefix_contents' => __( 'Please only enter letters, numbers or underscores for the new table prefix.', 'wp-migrate-db-pro-multisite-tools' ), 238 | 'export_subsite_option' => __( 'Export a subsite as a single site install', 'wp-migrate-db-pro-multisite-tools' ), 239 | 'pull_subsite_option' => __( 'Pull into a specific subsite', 'wp-migrate-db-pro-multisite-tools' ), 240 | 'push_subsite_option' => __( 'Push a specific subsite', 'wp-migrate-db-pro-multisite-tools' ), 241 | ); 242 | 243 | return $strings; 244 | } 245 | 246 | /** 247 | * Retrieve a specific translated string. 248 | * 249 | * @param string $key 250 | * 251 | * @return string 252 | */ 253 | public function get_string( $key ) { 254 | $strings = $this->get_strings(); 255 | 256 | return ( isset( $strings[ $key ] ) ) ? $strings[ $key ] : ''; 257 | } 258 | 259 | /** 260 | * Load multisite tools related assets in core plugin. 261 | */ 262 | public function load_assets() { 263 | $plugins_url = trailingslashit( plugins_url() ) . trailingslashit( $this->plugin_folder_name ); 264 | $version = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? time() : $this->plugin_version; 265 | $ver_string = '-' . str_replace( '.', '', $this->plugin_version ); 266 | $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; 267 | 268 | $src = $plugins_url . 'asset/dist/css/styles.css'; 269 | wp_enqueue_style( 'wp-migrate-db-pro-multisite-tools-styles', $src, array( 'wp-migrate-db-pro-styles' ), $version ); 270 | 271 | $src = $plugins_url . "asset/dist/js/script{$ver_string}{$min}.js"; 272 | wp_enqueue_script( 'wp-migrate-db-pro-multisite-tools-script', 273 | $src, 274 | array( 275 | 'jquery', 276 | 'wp-migrate-db-pro-common', 277 | 'wp-migrate-db-pro-hook', 278 | 'wp-migrate-db-pro-script', 279 | ), 280 | $version, 281 | true ); 282 | 283 | wp_localize_script( 'wp-migrate-db-pro-multisite-tools-script', 'wpmdbmst_strings', $this->get_strings() ); 284 | } 285 | 286 | /** 287 | * Adds extra information to the core plugin's diagnostic info 288 | */ 289 | public function diagnostic_info() { 290 | if ( is_multisite() ) { 291 | echo 'Sites: '; 292 | echo number_format( get_blog_count() ); 293 | echo "\r\n"; 294 | } 295 | } 296 | 297 | /** 298 | * Should the given table be excluded from a subsite migration. 299 | * 300 | * @param bool $exclude 301 | * @param string $table_name 302 | * 303 | * @return bool 304 | */ 305 | public function filter_table_for_subsite( $exclude, $table_name ) { 306 | if ( ! is_multisite() ) { 307 | return $exclude; 308 | } 309 | 310 | $blog_id = $this->selected_subsite(); 311 | 312 | if ( 0 < $blog_id ) { 313 | // wp_users and wp_usermeta are relevant to all sites, shortcut out. 314 | if ( $this->wpmdbpro->table_is( '', $table_name, 'non_ms_global' ) ) { 315 | return $exclude; 316 | } 317 | 318 | // Following tables are Multisite setup tables and can be excluded from migration. 319 | if ( $this->wpmdbpro->table_is( '', $table_name, 'ms_global' ) ) { 320 | return true; 321 | } 322 | 323 | global $wpdb; 324 | $prefix = $wpdb->base_prefix; 325 | $prefix_escaped = preg_quote( $prefix ); 326 | 327 | if ( 1 == $blog_id ) { 328 | // Exclude tables from non-primary subsites. 329 | if ( preg_match( '/^' . $prefix_escaped . '([0-9]+)_/', $table_name, $matches ) ) { 330 | $exclude = true; 331 | } 332 | } else { 333 | $prefix .= $blog_id . '_'; 334 | if ( 0 !== stripos( $table_name, $prefix ) ) { 335 | $exclude = true; 336 | } 337 | } 338 | } 339 | 340 | return $exclude; 341 | } 342 | 343 | /** 344 | * Filter the given tables if doing a subsite migration. 345 | * 346 | * @param array $tables 347 | * @param string $scope 348 | * 349 | * @return array 350 | */ 351 | public function filter_tables_for_subsite( $tables, $scope = 'regular' ) { 352 | if ( ! is_multisite() || empty( $tables ) ) { 353 | return $tables; 354 | } 355 | 356 | // We will not alter backup or temp tables list. 357 | if ( in_array( $scope, array( 'backup', 'temp' ) ) ) { 358 | return $tables; 359 | } 360 | 361 | $filtered_tables = array(); 362 | $blog_id = $this->selected_subsite(); 363 | 364 | if ( 0 < $blog_id ) { 365 | foreach ( $tables as $key => $value ) { 366 | if ( false === $this->filter_table_for_subsite( false, $value ) ) { 367 | $filtered_tables[ $key ] = $value; 368 | } 369 | } 370 | } else { 371 | $filtered_tables = $tables; 372 | } 373 | 374 | return $filtered_tables; 375 | } 376 | 377 | /** 378 | * Filter the given tables with sizes if doing a subsite migration. 379 | * 380 | * @param array $table_sizes 381 | * @param string $scope 382 | * 383 | * @return array 384 | */ 385 | public function filter_table_sizes_for_subsite( $table_sizes, $scope = 'regular' ) { 386 | if ( ! is_multisite() || empty( $table_sizes ) ) { 387 | return $table_sizes; 388 | } 389 | 390 | $tables = $this->filter_tables_for_subsite( array_keys( $table_sizes ), $scope ); 391 | 392 | return array_intersect_key( $table_sizes, array_flip( $tables ) ); 393 | } 394 | 395 | /** 396 | * Change the name of the given table if subsite selected and migration profile has new prefix. 397 | * 398 | * @param string $table_name 399 | * @param string $action 400 | * @param string $stage 401 | * @param array $site_details 402 | * 403 | * @return string 404 | */ 405 | public function filter_target_table_name( $table_name, $action, $stage, $site_details = array() ) { 406 | $blog_id = $this->selected_subsite(); 407 | 408 | if ( 1 > $blog_id || 'backup' == $stage ) { 409 | return $table_name; 410 | } 411 | 412 | $new_prefix = $this->wpmdbpro->profile_value( 'new_prefix' ); 413 | 414 | if ( empty( $new_prefix ) ) { 415 | return $table_name; 416 | } 417 | 418 | global $wpdb; 419 | $old_prefix = $wpdb->base_prefix; 420 | if ( is_multisite() && 1 < $blog_id && ! $this->wpmdbpro->table_is( '', $table_name, 'global', '', $blog_id ) ) { 421 | $old_prefix .= $blog_id . '_'; 422 | } 423 | 424 | // We do not want to overwrite the global tables unless exporting or target is a single site install. 425 | if ( 'savefile' !== $action && 426 | ( 427 | ( 'pull' === $action && 'true' === $site_details['local']['is_multisite'] ) || 428 | ( 'push' === $action && 'true' === $site_details['remote']['is_multisite'] ) 429 | ) && 430 | $this->wpmdbpro->table_is( '', $table_name, 'global' ) 431 | ) { 432 | $new_prefix .= 'wpmdbglobal_'; 433 | } 434 | 435 | if ( 0 === stripos( $table_name, $old_prefix ) ) { 436 | $table_name = substr_replace( $table_name, $new_prefix, 0, strlen( $old_prefix ) ); 437 | } 438 | 439 | return $table_name; 440 | } 441 | 442 | /** 443 | * Handler for the wpmdb_table_row filter. 444 | * The given $row can be modified, but if we return false the row will not be used. 445 | * 446 | * @param stdClass $row 447 | * @param string $table_name 448 | * @param string $action 449 | * @param string $stage 450 | * 451 | * @return bool 452 | */ 453 | public function filter_table_row( $row, $table_name, $action, $stage ) { 454 | $use = true; 455 | $blog_id = $this->selected_subsite(); 456 | 457 | if ( 1 > $blog_id || 'backup' == $stage ) { 458 | return $use; 459 | } 460 | 461 | $new_prefix = $this->wpmdbpro->profile_value( 'new_prefix' ); 462 | 463 | if ( empty( $new_prefix ) ) { 464 | return $row; 465 | } 466 | 467 | global $wpdb; 468 | 469 | $old_prefix = $wpdb->base_prefix; 470 | if ( is_multisite() && 1 < $blog_id ) { 471 | $old_prefix .= $blog_id . '_'; 472 | } 473 | 474 | if ( $this->wpmdbpro->table_is( 'options', $table_name ) ) { 475 | // Rename options records like wp_X_user_roles to wp_Y_user_roles otherwise no users can do anything in the migrated site. 476 | if ( 0 === stripos( $row->option_name, $old_prefix ) ) { 477 | $row->option_name = substr_replace( $row->option_name, $new_prefix, 0, strlen( $old_prefix ) ); 478 | } 479 | } 480 | 481 | if ( $this->wpmdbpro->table_is( 'usermeta', $table_name ) ) { 482 | if ( ! $this->is_user_required_for_blog( $row->user_id, $blog_id ) ) { 483 | $use = false; 484 | } elseif ( 1 == $blog_id ) { 485 | $prefix_escaped = preg_quote( $wpdb->base_prefix ); 486 | if ( 1 === preg_match( '/^' . $prefix_escaped . '([0-9]+)_/', $row->meta_key, $matches ) ) { 487 | // Remove non-primary subsite records from usermeta when migrating primary subsite. 488 | $use = false; 489 | } elseif ( 0 === stripos( $row->meta_key, $old_prefix ) ) { 490 | // Rename prefixed keys. 491 | $row->meta_key = substr_replace( $row->meta_key, $new_prefix, 0, strlen( $old_prefix ) ); 492 | } 493 | } else { 494 | if ( 0 === stripos( $row->meta_key, $old_prefix ) ) { 495 | // Rename prefixed keys. 496 | $row->meta_key = substr_replace( $row->meta_key, $new_prefix, 0, strlen( $old_prefix ) ); 497 | } elseif ( 0 === stripos( $row->meta_key, $wpdb->base_prefix ) ) { 498 | // Remove wp_* records from usermeta not for extracted subsite. 499 | $use = false; 500 | } 501 | } 502 | } 503 | 504 | if ( $this->wpmdbpro->table_is( 'users', $table_name ) ) { 505 | if ( ! $this->is_user_required_for_blog( $row->ID, $blog_id ) ) { 506 | $use = false; 507 | } 508 | } 509 | 510 | return $use; 511 | } 512 | 513 | /** 514 | * Handler for the wpmdb_find_and_replace filter. 515 | * 516 | * @param array $tmp_find_replace_pairs 517 | * @param string $intent 518 | * @param string $site_url 519 | * 520 | * @return array 521 | */ 522 | public function filter_find_and_replace( $tmp_find_replace_pairs, $intent, $site_url ) { 523 | // TODO: Remove this condition when MST usable from single site install. 524 | if ( ! is_multisite() ) { 525 | return $tmp_find_replace_pairs; 526 | } 527 | 528 | $blog_id = $this->selected_subsite(); 529 | 530 | if ( 1 > $blog_id ) { 531 | return $tmp_find_replace_pairs; 532 | } 533 | 534 | $source = ( 'pull' === $intent ) ? 'remote' : 'local'; 535 | $target = ( 'pull' === $intent ) ? 'local' : 'remote'; 536 | 537 | if ( 'true' === $this->state_data['site_details'][ $source ]['is_multisite'] ) { 538 | $source_site_url = $this->state_data['site_details'][ $source ]['subsites_info'][ $blog_id ]['site_url']; 539 | $source_uploads_baseurl = $this->state_data['site_details'][ $source ]['subsites_info'][ $blog_id ]['uploads']['baseurl']; 540 | $source_short_basedir = $this->state_data['site_details'][ $source ]['subsites_info'][ $blog_id ]['uploads']['short_basedir']; 541 | } else { 542 | $source_site_url = $this->state_data['site_details'][ $source ]['site_url']; 543 | $source_uploads_baseurl = $this->state_data['site_details'][ $source ]['uploads']['baseurl']; 544 | $source_short_basedir = ''; 545 | } 546 | $source_site_url = '//' . untrailingslashit( $this->scheme_less_url( $source_site_url ) ); 547 | $source_uploads_baseurl = '//' . untrailingslashit( $this->scheme_less_url( $source_uploads_baseurl ) ); 548 | 549 | if ( 'savefile' === $intent ) { 550 | $target_site_url = ''; 551 | $target_uploads_baseurl = ''; 552 | $target_short_basedir = ''; 553 | 554 | foreach ( $tmp_find_replace_pairs as $find => $replace ) { 555 | if ( $find == $source_site_url ) { 556 | $target_site_url = $replace; 557 | break; 558 | } 559 | } 560 | 561 | // Append extra path elements from uploads url, removing unneeded subsite specific elements. 562 | if ( ! empty( $target_site_url ) ) { 563 | $target_uploads_baseurl = $target_site_url . substr( $source_uploads_baseurl, strlen( $source_site_url ) ); 564 | 565 | if ( ! empty( $source_short_basedir ) ) { 566 | $target_uploads_baseurl = substr( untrailingslashit( $target_uploads_baseurl ), 0, -strlen( untrailingslashit( $source_short_basedir ) ) ); 567 | } 568 | } 569 | } elseif ( 'true' === $this->state_data['site_details'][ $target ]['is_multisite'] ) { 570 | $target_site_url = $this->state_data['site_details'][ $target ]['subsites_info'][ $blog_id ]['site_url']; 571 | $target_uploads_baseurl = $this->state_data['site_details'][ $target ]['subsites_info'][ $blog_id ]['uploads']['baseurl']; 572 | $target_short_basedir = $this->state_data['site_details'][ $target ]['subsites_info'][ $blog_id ]['uploads']['short_basedir']; 573 | } else { 574 | $target_site_url = $this->state_data['site_details'][ $target ]['site_url']; 575 | $target_uploads_baseurl = $this->state_data['site_details'][ $target ]['uploads']['baseurl']; 576 | $target_short_basedir = ''; 577 | } 578 | 579 | // If we have a target uploads url, we can add in the find/replace we need. 580 | if ( ! empty( $target_uploads_baseurl ) ) { 581 | $target_site_url = '//' . untrailingslashit( $this->scheme_less_url( $target_site_url ) ); 582 | $target_uploads_baseurl = '//' . untrailingslashit( $this->scheme_less_url( $target_uploads_baseurl ) ); 583 | 584 | // As we're appending to the find/replace rows, we need to use the already replaced values for altering uploads url. 585 | $old_uploads_url = substr_replace( $source_uploads_baseurl, $target_site_url, 0, strlen( $source_site_url ) ); 586 | $tmp_find_replace_pairs[ $old_uploads_url ] = $target_uploads_baseurl; 587 | } 588 | 589 | return $tmp_find_replace_pairs; 590 | } 591 | 592 | /** 593 | * Change the name of the given table depending on migration profile settings and source and target site setup. 594 | * 595 | * @param string $table_name 596 | * @param string $intent 597 | * @param array $site_details 598 | * 599 | * @return string 600 | * 601 | * This is run in response to the wpmdb_finalize_target_table_name filter on the target site. 602 | */ 603 | public function filter_finalize_target_table_name( $table_name, $intent, $site_details ) { 604 | $blog_id = $this->selected_subsite(); 605 | 606 | if ( 1 > $blog_id ) { 607 | return $table_name; 608 | } 609 | 610 | $new_prefix = $this->wpmdbpro->profile_value( 'new_prefix' ); 611 | 612 | if ( empty( $new_prefix ) ) { 613 | return $table_name; 614 | } 615 | 616 | // During a MST migration we add a custom prefix to the global tables so that we can manipulate their data before use. 617 | if ( is_multisite() && $this->wpmdbpro->table_is( '', $table_name, 'global', $new_prefix, $blog_id ) ) { 618 | $new_prefix .= 'wpmdbglobal_'; 619 | } 620 | 621 | $old_prefix = ( 'pull' === $intent ? $site_details['remote']['prefix'] : $site_details['local']['prefix'] ); 622 | if ( ! is_multisite() && 1 < $blog_id && ! $this->wpmdbpro->table_is( '', $table_name, 'global', $new_prefix, $blog_id ) ) { 623 | $old_prefix .= $blog_id . '_'; 624 | } 625 | 626 | if ( 0 === stripos( $table_name, $old_prefix ) ) { 627 | $table_name = substr_replace( $table_name, $new_prefix, 0, strlen( $old_prefix ) ); 628 | } 629 | 630 | return $table_name; 631 | } 632 | 633 | /** 634 | * Returns validated and sanitized form data. 635 | * 636 | * @param array|string $data 637 | * 638 | * @return array|string 639 | */ 640 | public function parse_migration_form_data( $data ) { 641 | $form_data = parent::parse_migration_form_data( $data ); 642 | 643 | $form_data = array_intersect_key( $form_data, array_flip( $this->accepted_fields ) ); 644 | 645 | return $form_data; 646 | } 647 | 648 | /** 649 | * Alter given destination file path depending on local and remote site setup. 650 | * 651 | * @param string $file_path 652 | * @param string $intent 653 | * @param WPMDBPro_Media_Files_Base $wpmdbmf 654 | * 655 | * @return string 656 | */ 657 | public function filter_mf_destination_file_path( $file_path, $intent, $wpmdbmf ) { 658 | $blog_id = $this->selected_subsite( $wpmdbmf ); 659 | 660 | if ( 1 > $blog_id ) { 661 | return $file_path; 662 | } 663 | 664 | if ( ! is_multisite() && 'push' === $intent && ! empty( $this->state_data['site_details']['local']['subsites_info'][ $blog_id ]['uploads']['short_basedir'] ) ) { 665 | $file_path = substr_replace( $file_path, '', 0, strlen( $this->state_data['site_details']['local']['subsites_info'][ $blog_id ]['uploads']['short_basedir'] ) ); 666 | } 667 | 668 | return $file_path; 669 | } 670 | 671 | /** 672 | * Alter given destination file path depending on local and remote site setup. 673 | * 674 | * @param string $file 675 | * @param string $intent 676 | * @param WPMDBPro_Media_Files_Base $wpmdbmf 677 | * 678 | * @return string 679 | */ 680 | public function filter_mf_file_not_on_local( $file, $intent, $wpmdbmf ) { 681 | $blog_id = $this->selected_subsite( $wpmdbmf ); 682 | 683 | if ( 1 > $blog_id ) { 684 | return $file; 685 | } 686 | 687 | if ( is_multisite() && 'push' === $intent && 688 | $this->state_data['site_details']['local']['is_multisite'] !== $this->state_data['site_details']['remote']['is_multisite'] && 689 | ! empty( $this->state_data['site_details']['local']['subsites_info'][ $blog_id ]['uploads']['short_basedir'] ) 690 | ) { 691 | $file = $this->state_data['site_details']['local']['subsites_info'][ $blog_id ]['uploads']['short_basedir'] . $file; 692 | } elseif ( ! is_multisite() && 'pull' === $intent && 693 | $this->state_data['site_details']['local']['is_multisite'] !== $this->state_data['site_details']['remote']['is_multisite'] && 694 | ! empty( $this->state_data['site_details']['local']['subsites_info'][ $blog_id ]['uploads']['short_basedir'] ) 695 | ) { 696 | $file = substr( $file, strlen( $this->state_data['site_details']['local']['subsites_info'][ $blog_id ]['uploads']['short_basedir'] ) ); 697 | } 698 | 699 | return $file; 700 | } 701 | 702 | /** 703 | * Alter given source file path depending on local and remote site setup. 704 | * 705 | * @param array $response 706 | * @param string $intent 707 | * @param WPMDBPro_Media_Files_Base $wpmdbmf 708 | * 709 | * @return string 710 | */ 711 | public function filter_mf_get_remote_attachment_batch_response( $response, $intent, $wpmdbmf ) { 712 | $blog_id = $this->selected_subsite( $wpmdbmf ); 713 | 714 | if ( 1 > $blog_id ) { 715 | return $response; 716 | } 717 | 718 | if ( is_multisite() && 'pull' === $intent && 719 | $this->state_data['site_details']['local']['is_multisite'] !== $this->state_data['site_details']['remote']['is_multisite'] 720 | ) { 721 | $remote_attachments = unserialize( stripslashes( $response['remote_attachments'] ) ); 722 | 723 | if ( ! empty( $remote_attachments[1] ) ) { 724 | foreach ( $remote_attachments[1] as $index => $attachment ) { 725 | $attachment['blog_id'] = $blog_id; 726 | $attachment['file'] = ltrim( trailingslashit( $this->state_data['site_details']['local']['subsites_info'][ $blog_id ]['uploads']['short_basedir'] ) . $attachment['file'], '/' ); 727 | 728 | if ( ! empty( $attachment['sizes'] ) ) { 729 | foreach ( $attachment['sizes'] as $size_idx => $size ) { 730 | $attachment['sizes'][ $size_idx ]['file'] = ltrim( trailingslashit( $this->state_data['site_details']['local']['subsites_info'][ $blog_id ]['uploads']['short_basedir'] ) . $size['file'], '/' ); 731 | } 732 | } 733 | 734 | $remote_attachments[1][ $index ] = $attachment; 735 | } 736 | } 737 | $response['remote_attachments'] = addslashes( serialize( $remote_attachments ) ); 738 | } 739 | 740 | return $response; 741 | } 742 | 743 | /** 744 | * Alter given source file path depending on local and remote site setup. 745 | * 746 | * @param string $file 747 | * @param string $intent 748 | * @param WPMDBPro_Media_Files_Base $wpmdbmf 749 | * 750 | * @return string 751 | */ 752 | public function filter_mf_file_to_download( $file, $intent, $wpmdbmf ) { 753 | $blog_id = $this->selected_subsite( $wpmdbmf ); 754 | 755 | if ( 1 > $blog_id ) { 756 | return $file; 757 | } 758 | 759 | if ( is_multisite() && 'pull' === $intent && 760 | $this->state_data['site_details']['local']['is_multisite'] !== $this->state_data['site_details']['remote']['is_multisite'] && 761 | ! empty( $this->state_data['site_details']['local']['subsites_info'][ $blog_id ]['uploads']['short_basedir'] ) 762 | ) { 763 | $file = substr( $file, strlen( $this->state_data['site_details']['local']['subsites_info'][ $blog_id ]['uploads']['short_basedir'] ) ); 764 | } 765 | 766 | return $file; 767 | } 768 | 769 | /** 770 | * Should the given file be excluded from removal? 771 | * 772 | * @param bool $value 773 | * @param string $upload_dir 774 | * @param string $short_file_path 775 | * @param WPMDBPro_Media_Files_Base $wpmdbmf 776 | * 777 | * @return bool 778 | */ 779 | public function filter_mf_exclude_local_media_file_from_removal( $value, $upload_dir, $short_file_path, $wpmdbmf ) { 780 | // Already excluded, don't override. 781 | if ( $value ) { 782 | return $value; 783 | } 784 | 785 | $blog_id = $this->selected_subsite( $wpmdbmf ); 786 | 787 | if ( 1 > $blog_id ) { 788 | return $value; 789 | } 790 | 791 | if ( is_multisite() && 'pull' === $this->state_data['intent'] && 792 | ! empty( $this->state_data['site_details']['local']['subsites_info'][ $blog_id ]['uploads']['basedir'] ) 793 | ) { 794 | $file_given = $upload_dir . $short_file_path; 795 | $file_munged = trailingslashit( $this->state_data['site_details']['local']['subsites_info'][ $blog_id ]['uploads']['basedir'] ) . substr( $short_file_path, strlen( $this->state_data['site_details']['local']['subsites_info'][ $blog_id ]['uploads']['short_basedir'] ) ); 796 | 797 | if ( $file_given !== $file_munged ) { 798 | $value = true; 799 | } 800 | } 801 | 802 | return $value; 803 | } 804 | 805 | /** 806 | * Handler for "wpmdbmf_include_subsite" filter to disallow subsite's media to be migrated if not selected. 807 | * 808 | * @param bool $value 809 | * @param int $blog_id 810 | * @param WPMDBPro_Media_Files_Base $wpmdbmf 811 | * 812 | * @return bool 813 | */ 814 | public function include_subsite( $value, $blog_id, $wpmdbmf ) { 815 | $selected_blog_id = $this->selected_subsite( $wpmdbmf ); 816 | 817 | if ( 1 > $selected_blog_id ) { 818 | return $value; 819 | } 820 | 821 | if ( $blog_id !== $selected_blog_id ) { 822 | $value = false; 823 | } 824 | 825 | return $value; 826 | } 827 | 828 | /** 829 | * Maybe change options keys to be preserved. 830 | * 831 | * @param array $preserved_options 832 | * @param string $intent 833 | * 834 | * @return array 835 | */ 836 | public function filter_preserved_options( $preserved_options, $intent = '' ) { 837 | $blog_id = $this->selected_subsite(); 838 | 839 | if ( 1 > $blog_id || 'push' !== $intent ) { 840 | return $preserved_options; 841 | } 842 | 843 | $keep_active_plugins = $this->profile_value( 'keep_active_plugins' ); 844 | 845 | if ( empty( $keep_active_plugins ) ) { 846 | $preserved_options[] = 'active_plugins'; 847 | } 848 | 849 | return $preserved_options; 850 | } 851 | 852 | /** 853 | * Maybe preserve the WPMDB plugins if they aren't already preserved. 854 | * 855 | * @param array $preserved_options_data 856 | * @param string $intent 857 | * 858 | * @return array 859 | */ 860 | public function filter_preserved_options_data( $preserved_options_data, $intent = '' ) { 861 | $blog_id = $this->selected_subsite(); 862 | 863 | $keep_active_plugins = $this->profile_value( 'keep_active_plugins' ); 864 | 865 | if ( 1 > $blog_id || 'push' !== $intent || ! empty( $keep_active_plugins ) ) { 866 | return $preserved_options_data; 867 | } 868 | 869 | if ( ! empty( $preserved_options_data ) ) { 870 | foreach ( $preserved_options_data as $table => $data ) { 871 | foreach ( $data as $key => $option ) { 872 | if ( 'active_plugins' === $option['option_name'] ) { 873 | global $wpdb; 874 | 875 | $table_name = esc_sql( $table ); 876 | $option_value = unserialize( $option['option_value'] ); 877 | $migrated_plugins = array(); 878 | $wpmdb_plugins = array(); 879 | 880 | if ( $result = $wpdb->get_var( "SELECT option_value FROM $table_name WHERE option_name = 'active_plugins'" ) ) { 881 | $unserialized = unserialize( $result ); 882 | if ( is_array( $unserialized ) ) { 883 | $migrated_plugins = $unserialized; 884 | } 885 | } 886 | 887 | foreach ( $option_value as $plugin_key => $plugin ) { 888 | if ( 0 === strpos( $plugin, 'wp-migrate-db' ) ) { 889 | $wpmdb_plugins[] = $plugin; 890 | } 891 | } 892 | 893 | $merged_plugins = array_unique( array_merge( $wpmdb_plugins, $migrated_plugins ) ); 894 | $option['option_value'] = serialize( $merged_plugins ); 895 | $preserved_options_data[ $table ][ $key ] = $option; 896 | break; 897 | } 898 | } 899 | } 900 | } 901 | 902 | return $preserved_options_data; 903 | } 904 | 905 | /** 906 | * Append more queries to be run at finalize_migration. 907 | * 908 | * @param array $queries 909 | * 910 | * @return array 911 | */ 912 | public function filter_get_alter_queries( $queries ) { 913 | $blog_id = $this->selected_subsite(); 914 | 915 | if ( 1 > $blog_id ) { 916 | return $queries; 917 | } 918 | 919 | if ( is_multisite() && 'pull' === $this->state_data['intent'] && ! empty( $this->state_data['tables'] ) ) { 920 | global $wpdb; 921 | 922 | $tables = explode( ',', $this->state_data['tables'] ); 923 | 924 | $target_users_table = null; 925 | $source_users_table = null; 926 | $target_usermeta_table = null; 927 | $source_usermeta_table = null; 928 | $posts_imported = false; 929 | $target_posts_table = null; 930 | $comments_imported = false; 931 | $target_comments_table = null; 932 | foreach ( $tables as $table ) { 933 | if ( empty( $source_users_table ) && $this->wpmdbpro->table_is( 'users', $table ) ) { 934 | $target_users_table = $table; 935 | $source_users_table = $this->filter_finalize_target_table_name( $table, $this->state_data['intent'], $this->state_data['site_details'] ); 936 | continue; 937 | } 938 | if ( empty( $source_usermeta_table ) && $this->wpmdbpro->table_is( 'usermeta', $table ) ) { 939 | $target_usermeta_table = $table; 940 | $source_usermeta_table = $this->filter_finalize_target_table_name( $table, $this->state_data['intent'], $this->state_data['site_details'] ); 941 | continue; 942 | } 943 | if ( ! $posts_imported && $this->wpmdbpro->table_is( 'posts', $table ) ) { 944 | $posts_imported = true; 945 | $target_posts_table = $this->filter_finalize_target_table_name( $table, $this->state_data['intent'], $this->state_data['site_details'] ); 946 | continue; 947 | } 948 | if ( ! $comments_imported && $this->wpmdbpro->table_is( 'comments', $table ) ) { 949 | $comments_imported = true; 950 | $target_comments_table = $this->filter_finalize_target_table_name( $table, $this->state_data['intent'], $this->state_data['site_details'] ); 951 | continue; 952 | } 953 | } 954 | 955 | // Find users that already exist and update their content to adopt existing user id and remove from import. 956 | if ( ! empty( $source_users_table ) ) { 957 | $updated_user_ids = array(); 958 | $temp_prefix = $this->state_data['temp_prefix']; 959 | $temp_source_users_table = $temp_prefix . $source_users_table; 960 | 961 | $sql = " 962 | SELECT source.id AS source_id, target.id AS target_id FROM `{$temp_source_users_table}` AS source, `{$target_users_table}` AS target 963 | WHERE target.user_login = source.user_login 964 | AND target.user_email = source.user_email 965 | "; 966 | 967 | $user_ids_to_update = $wpdb->get_results( $sql, ARRAY_A ); 968 | 969 | if ( ! empty( $user_ids_to_update ) ) { 970 | foreach ( $user_ids_to_update as $user_ids ) { 971 | $blogs_of_user = get_blogs_of_user( $user_ids['target_id'] ); 972 | 973 | if ( empty( $blogs_of_user ) || array_key_exists( $blog_id, $blogs_of_user ) ) { 974 | // Only update content ownership if user id has changed. 975 | if ( $user_ids['source_id'] !== $user_ids['target_id'] ) { 976 | if ( $posts_imported ) { 977 | $queries[]['query'] = " 978 | UPDATE `{$target_posts_table}` 979 | SET post_author = {$user_ids['target_id']} 980 | WHERE post_author = {$user_ids['source_id']} 981 | ;\n 982 | "; 983 | } 984 | 985 | if ( $comments_imported ) { 986 | $queries[]['query'] = " 987 | UPDATE `{$target_comments_table}` 988 | SET user_id = {$user_ids['target_id']} 989 | WHERE user_id = {$user_ids['source_id']} 990 | ;\n 991 | "; 992 | } 993 | } 994 | 995 | // Log user for exclusion from import. 996 | $updated_user_ids[] = $user_ids['source_id']; 997 | } 998 | } 999 | } 1000 | 1001 | $queries[]['query'] = "ALTER TABLE `{$target_users_table}` ADD COLUMN wpmdb_user_id BIGINT(20) UNSIGNED;\n"; 1002 | 1003 | $where = ''; 1004 | if ( ! empty( $updated_user_ids ) ) { 1005 | $where = 'WHERE u2.id NOT IN (' . join( ',', $updated_user_ids ) . ')'; 1006 | } 1007 | $queries[]['query'] = "INSERT INTO `{$target_users_table}` (user_login, user_pass, user_nicename, user_email, user_url, user_registered, user_activation_key, user_status, display_name, wpmdb_user_id) 1008 | SELECT u2.user_login, u2.user_pass, u2.user_nicename, u2.user_email, u2.user_url, u2.user_registered, u2.user_activation_key, u2.user_status, u2.display_name, u2.id 1009 | FROM `{$source_users_table}` AS u2 1010 | {$where};\n"; 1011 | 1012 | if ( ! empty( $source_usermeta_table ) ) { 1013 | $queries[]['query'] = "INSERT INTO `{$target_usermeta_table}` (user_id, meta_key, meta_value) 1014 | SELECT u.id, m2.meta_key, m2.meta_value 1015 | FROM `{$source_usermeta_table}` AS m2 1016 | JOIN `{$target_users_table}` AS u ON m2.user_id = u.wpmdb_user_id;\n"; 1017 | } 1018 | 1019 | if ( $posts_imported ) { 1020 | $queries[]['query'] = " 1021 | UPDATE `{$target_posts_table}` AS p, `{$target_users_table}` AS u 1022 | SET p.post_author = u.id 1023 | WHERE p.post_author = u.wpmdb_user_id 1024 | ;\n"; 1025 | } 1026 | 1027 | if ( $comments_imported ) { 1028 | $queries[]['query'] = " 1029 | UPDATE `{$target_comments_table}` AS c, `{$target_users_table}` AS u 1030 | SET c.user_id = u.id 1031 | WHERE c.user_id = u.wpmdb_user_id 1032 | ;\n"; 1033 | } 1034 | $queries[]['query'] = "DROP TABLE `{$source_users_table}`;\n"; 1035 | 1036 | $queries[]['query'] = "ALTER TABLE `{$target_users_table}` DROP COLUMN wpmdb_user_id;\n"; 1037 | } 1038 | 1039 | // Cleanup imported usermeta table, whether used by above user related queries or not. 1040 | // TODO: Maybe support updating usermeta without imported users table? 1041 | if ( ! empty( $source_usermeta_table ) ) { 1042 | $queries[]['query'] = "DROP TABLE `{$source_usermeta_table}`;\n"; 1043 | } 1044 | } 1045 | 1046 | return $queries; 1047 | } 1048 | 1049 | /** 1050 | * Does the passed subsite (ID) exist? 1051 | * 1052 | * @param int $blog_id 1053 | * 1054 | * @return bool 1055 | */ 1056 | public function subsite_exists( $blog_id ) { 1057 | if ( ! is_multisite() ) { 1058 | return false; 1059 | } 1060 | 1061 | $blogs = wp_get_sites( array( 'limit' => 0 ) ); 1062 | 1063 | if ( empty( $blogs ) ) { 1064 | return false; 1065 | } 1066 | 1067 | foreach ( $blogs as $blog ) { 1068 | if ( ! empty( $blog['blog_id'] ) && $blog_id == $blog['blog_id'] ) { 1069 | return true; 1070 | } 1071 | } 1072 | 1073 | return false; 1074 | } 1075 | } 1076 | -------------------------------------------------------------------------------- /languages/wp-migrate-db-pro-multisite-tools-en.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: wp-migrate-db-pro-multisite-tools\n" 10 | "Report-Msgid-Bugs-To: nom@deliciousbrains.com\n" 11 | "POT-Creation-Date: 2016-07-11 18:30-0700\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=CHARSET\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: class/cli/wpmdbpro-multisite-tools-cli.php:49 21 | msgid "" 22 | "The installation must be a Multisite network to make use of the subsite " 23 | "option" 24 | msgstr "" 25 | 26 | #: class/cli/wpmdbpro-multisite-tools-cli.php:52 27 | #: class/cli/wpmdbpro-multisite-tools-cli.php:57 28 | msgid "" 29 | "A valid Blog ID or Subsite URL must be supplied to make use of the subsite " 30 | "option" 31 | msgstr "" 32 | 33 | #: class/cli/wpmdbpro-multisite-tools-cli.php:68 34 | msgid "A new table name prefix may only be specified for subsite exports." 35 | msgstr "" 36 | 37 | #: class/cli/wpmdbpro-multisite-tools-cli.php:71 38 | msgid "A valid prefix must be supplied to make use of the prefix option" 39 | msgstr "" 40 | 41 | #: class/wpmdbpro-multisite-tools.php:234 42 | msgid "Migration failed" 43 | msgstr "" 44 | 45 | #: class/wpmdbpro-multisite-tools.php:235 46 | msgid "Please select a subsite." 47 | msgstr "" 48 | 49 | #: class/wpmdbpro-multisite-tools.php:236 50 | msgid "Please enter a new table prefix." 51 | msgstr "" 52 | 53 | #: class/wpmdbpro-multisite-tools.php:237 54 | msgid "" 55 | "Please only enter letters, numbers or underscores for the new table prefix." 56 | msgstr "" 57 | 58 | #: class/wpmdbpro-multisite-tools.php:238 59 | msgid "Export a subsite as a single site install" 60 | msgstr "" 61 | 62 | #: class/wpmdbpro-multisite-tools.php:239 63 | msgid "Pull into a specific subsite" 64 | msgstr "" 65 | 66 | #: class/wpmdbpro-multisite-tools.php:240 67 | msgid "Push a specific subsite" 68 | msgstr "" 69 | 70 | #: template/migrate.php:28 71 | msgid "Select a subsite" 72 | msgstr "" 73 | 74 | #: template/migrate.php:46 75 | msgid "New Table Name Prefix" 76 | msgstr "" 77 | 78 | #: template/migrate.php:48 79 | msgid "New Prefix" 80 | msgstr "" 81 | 82 | #: template/migrate.php:55 83 | msgid "Addon Missing" 84 | msgstr "" 85 | 86 | #: template/migrate.php:55 87 | msgid "" 88 | "The Multisite Tools addon is inactive on the remote site. " 89 | "Please install and activate it to enable subsite migrations." 90 | msgstr "" 91 | 92 | #: template/migrate.php:59 93 | msgid "Version Mismatch" 94 | msgstr "" 95 | 96 | #: template/migrate.php:59 97 | #, php-format 98 | msgid "" 99 | "We have detected you have version " 100 | "of WP Migrate DB Pro Multisite Tools at but are using %1$s here. Please go to the Plugins page on both installs and check for updates." 103 | msgstr "" 104 | 105 | #: template/migrate.php:63 106 | msgid "Different Table Prefixes" 107 | msgstr "" 108 | 109 | #: template/migrate.php:63 110 | #, php-format 111 | msgid "" 112 | "We have detected you have table prefix \"\" at but have \"%1$s\" " 114 | "here. Multisite Tools currently only supports migrating subsites between " 115 | "sites with the same base table prefix." 116 | msgstr "" 117 | -------------------------------------------------------------------------------- /template/migrate.php: -------------------------------------------------------------------------------- 1 | is_valid_licence() ) { 3 | global $loaded_profile, $wpdb; 4 | $table_prefix = $wpdb->base_prefix; 5 | 6 | if ( isset( $loaded_profile['multisite_subsite_export'] ) ) { 7 | $loaded_profile['mst_select_subsite'] = $loaded_profile['multisite_subsite_export']; 8 | } 9 | 10 | if ( isset( $loaded_profile['select_subsite'] ) ) { 11 | $loaded_profile['mst_selected_subsite'] = $loaded_profile['select_subsite']; 12 | } 13 | ?> 14 |
15 | 16 | 22 | 23 |
24 | 43 | 44 |
45 | 51 |
52 |
53 | 54 | 57 | 58 | 61 | 62 | 65 |
66 |