├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── asset └── js │ └── script.js ├── class └── wpsdb-media-files.php ├── composer.json ├── languages └── wp-migrate-db-pro-media-files-en.pot ├── template └── migrate.php ├── version.php └── wp-sync-db-media-files.php /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .htaccess 2 | wp-config.php 3 | wp-content/uploads/ 4 | wp-content/blogs.dir/ 5 | wp-content/upgrade/ 6 | wp-content/backup-db/ 7 | wp-content/advanced-cache.php 8 | wp-content/wp-cache-config.php 9 | sitemap.xml 10 | *.log 11 | wp-content/cache/ 12 | wp-content/backups/ 13 | sitemap.xml.gz 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WP Sync DB Media Files 2 | An addon for [WP Sync DB](https://github.com/wp-sync-db/wp-sync-db) that lets you sync media libraries between WordPress installations. 3 | 4 |

5 | -------------------------------------------------------------------------------- /asset/js/script.js: -------------------------------------------------------------------------------- 1 | // functions 2 | var determine_media_to_migrate; 3 | var remote_media_files_unavailable = false; 4 | var remote_connection_data; 5 | var connection_info; 6 | var media_successfully_determined; 7 | 8 | (function($) { 9 | 10 | // .length doesn't work on JS "associative arrays" i.e. objects with key/value elements, this does 11 | Object.size = function(obj) { 12 | var size = 0, 13 | key; 14 | for (key in obj) { 15 | if (obj.hasOwnProperty(key)) size++; 16 | } 17 | return size; 18 | }; 19 | 20 | $(document).ready(function() { 21 | 22 | if (migration_type() == 'savefile') { 23 | $('.media-files-options').hide(); 24 | } 25 | 26 | var disable_media_files_option = function() { 27 | $('#media-files').attr('data-available', '0'); 28 | $('#media-files').prop('checked', false); 29 | $('#media-files').attr('disabled', 'disabled'); 30 | $('.media-files').addClass('disabled'); 31 | $('.media-files-options .expandable-content').hide(); 32 | }; 33 | 34 | var hide_show_options = function(unavailable) { 35 | var mig_type = migration_type(); 36 | 37 | if ('savefile' == mig_type) { 38 | $('.media-files-options').hide(); 39 | return; 40 | } 41 | 42 | $('.media-files-options').show(); 43 | $('.media-files-push').hide(); 44 | 45 | if (unavailable) { 46 | $('.media-files-options ul').hide(); 47 | $('.media-migration-unavailable').show(); 48 | disable_media_files_option(); 49 | return; 50 | } 51 | 52 | if (typeof remote_connection_data != 'undefined' && 53 | wpsdb_media_files_version != remote_connection_data.media_files_version 54 | ) { 55 | $('.media-files-remote-location').html(remote_connection_data.url); 56 | $('.media-file-remote-version').html(remote_connection_data.media_files_version); 57 | $('.media-files-different-plugin-version-notice').show(); 58 | disable_media_files_option(); 59 | return; 60 | } 61 | 62 | $('.media-files-options ul').show(); 63 | $('.media-migration-unavailable').hide(); 64 | $('.media-files-different-plugin-version-notice').hide(); 65 | $('#media-files').removeAttr('disabled'); 66 | $('.media-files').removeClass('disabled'); 67 | $('#media-files').attr('data-available', '1'); 68 | }; 69 | 70 | $.wpsdb.add_action('move_connection_info_box', function() { 71 | hide_show_options(remote_media_files_unavailable); 72 | $('.remove-scope-1').html('remote'); 73 | $('.remove-scope-2').html('local'); 74 | if (migration_type() == 'pull') { 75 | $('.remove-scope-1').html('local'); 76 | $('.remove-scope-2').html('remote'); 77 | } 78 | }); 79 | 80 | $.wpsdb.add_action('verify_connection_to_remote_site', function( 81 | connection_data) { 82 | remote_connection_data = connection_data; 83 | remote_media_files_unavailable = (typeof connection_data.media_files_available == 84 | 'undefined'); 85 | hide_show_options(remote_media_files_unavailable); 86 | }); 87 | 88 | $.wpsdb.add_filter('wpsdb_before_migration_complete_hooks', function( 89 | hooks) { 90 | if (false == is_media_migration() || 'savefile' == migration_type()) 91 | return hooks; 92 | hooks.push('determine_media_to_migrate'); 93 | return hooks; 94 | }); 95 | 96 | determine_media_to_migrate = function() { 97 | connection_info = $.trim($('.pull-push-connection-info').val()).split( 98 | "\n"); 99 | $('.progress-text').html(wpsdbmf_strings.determining); 100 | 101 | var remove_local_media = 0; 102 | 103 | if ($('#remove-local-media').is(':checked')) { 104 | remove_local_media = 1; 105 | } 106 | 107 | $.ajax({ 108 | url: ajaxurl, 109 | type: 'POST', 110 | dataType: 'text', 111 | cache: false, 112 | data: { 113 | action: 'wpsdbmf_determine_media_to_migrate', 114 | remove_local_media: remove_local_media, 115 | intent: migration_type(), 116 | url: connection_info[0], 117 | key: connection_info[1], 118 | temp_prefix: connection_data.temp_prefix, 119 | nonce: wpsdb_nonces.determine_media_to_migrate, 120 | }, 121 | error: function(jqXHR, textStatus, errorThrown) { 122 | $('.progress-title').html(wpsdbmf_strings.migration_failed); 123 | $('.progress-text').html(wpsdbmf_strings.error_determining + 124 | ' (#101mf)'); 125 | $('.progress-text').addClass('migration-error'); 126 | console.log(jqXHR); 127 | console.log(textStatus); 128 | console.log(errorThrown); 129 | migration_error = true; 130 | migration_complete_events(); 131 | return; 132 | }, 133 | success: function(data) { 134 | original_data = data; 135 | data = JSON.parse(data.trim()); 136 | if (false == data) { 137 | migration_failed(original_data); 138 | return; 139 | } 140 | 141 | next_step_in_migration = { 142 | fn: media_successfully_determined, 143 | args: [data] 144 | }; 145 | execute_next_step(); 146 | } 147 | 148 | }); 149 | 150 | } 151 | 152 | function migration_failed(data) { 153 | $('.progress-title').html(wpsdbmf_strings.migration_failed); 154 | $('.progress-text').html(data); 155 | $('.progress-text').addClass('migration-error'); 156 | migration_error = true; 157 | migration_complete_events(); 158 | } 159 | 160 | media_successfully_determined = function(data) { 161 | if (typeof data.wpsdb_error != 'undefined' && data.wpsdb_error == 1) { 162 | non_fatal_errors += data.body; 163 | next_step_in_migration = { 164 | fn: wpsdb_call_next_hook 165 | }; 166 | execute_next_step(); 167 | return; 168 | } 169 | 170 | var args = {}; 171 | args.media_progress = 0; 172 | args.media_progress_image_number = 0; 173 | args.media_total_size = data.total_size; 174 | args.remote_uploads_url = data.remote_uploads_url; 175 | args.files_to_migrate = data.files_to_migrate; 176 | 177 | args.bottleneck = wpsdb_max_request; 178 | 179 | if (Object.size(args.files_to_migrate) > 0) { 180 | $('.progress-bar').width('0px'); 181 | } 182 | 183 | $('.progress-tables').empty(); 184 | $('.progress-tables-hover-boxes').empty(); 185 | 186 | $('.progress-tables').prepend('
' + 188 | wpsdbmf_strings.media_files + 189 | ' (0 / ' + 190 | wpsdb_add_commas(Object.size(args.files_to_migrate)) + 191 | ')
'); 192 | 193 | next_step_in_migration = { 194 | fn: migrate_media_files_recursive, 195 | args: [args] 196 | }; 197 | execute_next_step(); 198 | } 199 | 200 | function migrate_media_files_recursive(args) { 201 | if (0 == Object.size(args.files_to_migrate)) { 202 | wpsdb_call_next_hook(); 203 | return; 204 | } 205 | 206 | var file_chunk_to_migrate = []; 207 | var file_chunk_size = 0; 208 | var number_of_files_to_migrate = 0; 209 | 210 | $.each(args.files_to_migrate, function(index, value) { 211 | if (!file_chunk_to_migrate.length) { 212 | file_chunk_to_migrate.push(index); 213 | file_chunk_size += value; 214 | delete args.files_to_migrate[index]; 215 | ++args.media_progress_image_number; 216 | ++number_of_files_to_migrate; 217 | } else { 218 | if ((file_chunk_size + value) > args.bottleneck || 219 | number_of_files_to_migrate >= remote_connection_data.media_files_max_file_uploads 220 | ) { 221 | return false; 222 | } else { 223 | file_chunk_to_migrate.push(index); 224 | file_chunk_size += value; 225 | delete args.files_to_migrate[index]; 226 | ++args.media_progress_image_number; 227 | ++number_of_files_to_migrate; 228 | } 229 | } 230 | }); 231 | 232 | var connection_info = $.trim($('.pull-push-connection-info').val()).split( 233 | "\n"); 234 | 235 | $.ajax({ 236 | url: ajaxurl, 237 | type: 'POST', 238 | dataType: 'text', 239 | cache: false, 240 | data: { 241 | action: 'wpsdbmf_migrate_media', 242 | file_chunk: file_chunk_to_migrate, 243 | remote_uploads_url: args.remote_uploads_url, 244 | intent: migration_type(), 245 | url: connection_info[0], 246 | key: connection_info[1], 247 | nonce: wpsdb_nonces.migrate_media, 248 | }, 249 | error: function(jqXHR, textStatus, errorThrown) { 250 | $('.progress-title').html('Migration failed'); 251 | $('.progress-text').html(wpsdbmf_strings.problem_migrating_media + 252 | ' (#102mf)'); 253 | $('.progress-text').addClass('migration-error'); 254 | console.log(jqXHR); 255 | console.log(textStatus); 256 | console.log(errorThrown); 257 | migration_error = true; 258 | migration_complete_events(); 259 | return; 260 | }, 261 | success: function(data) { 262 | original_data = data; 263 | data = JSON.parse(data.trim()); 264 | if (false == data) { 265 | migration_failed(original_data); 266 | return; 267 | } 268 | 269 | if (typeof data.wpsdb_error != 'undefined' && data.wpsdb_error == 270 | 1) { 271 | non_fatal_errors += data.body; 272 | } 273 | 274 | args.media_progress += file_chunk_size; 275 | 276 | var percent = 100 * args.media_progress / args.media_total_size; 277 | $('.progress-bar').width(percent + '%'); 278 | overall_percent = Math.floor(percent); 279 | 280 | $('.progress-text').html(overall_percent + '% - ' + 281 | wpsdbmf_strings.migrating_media_files); 282 | $('.media-migration-current-image').html(wpsdb_add_commas(args.media_progress_image_number)); 283 | 284 | next_step_in_migration = { 285 | fn: migrate_media_files_recursive, 286 | args: [args] 287 | }; 288 | execute_next_step(); 289 | } 290 | }); 291 | } 292 | 293 | function is_media_migration() { 294 | return $('#media-files').attr('data-available') == '1' && $( 295 | '#media-files').is(':checked') ? true : false; 296 | } 297 | 298 | function migration_type() { 299 | return $('input[name=action]:checked').val(); 300 | } 301 | }); 302 | })(jQuery); 303 | -------------------------------------------------------------------------------- /class/wpsdb-media-files.php: -------------------------------------------------------------------------------- 1 | plugin_slug = 'wp-sync-db-media-files'; 10 | $this->plugin_version = $GLOBALS['wpsdb_meta']['wp-sync-db-media-files']['version']; 11 | 12 | if( ! $this->meets_version_requirements( '1.4b1' ) ) return; 13 | 14 | add_action( 'wpsdb_after_advanced_options', array( $this, 'migration_form_controls' ) ); 15 | add_action( 'wpsdb_load_assets', array( $this, 'load_assets' ) ); 16 | add_action( 'wpsdb_js_variables', array( $this, 'js_variables' ) ); 17 | add_filter( 'wpsdb_accepted_profile_fields', array( $this, 'accepted_profile_fields' ) ); 18 | add_filter( 'wpsdb_establish_remote_connection_data', array( $this, 'establish_remote_connection_data' ) ); 19 | add_filter( 'wpsdb_nonces', array( $this, 'add_nonces' ) ); 20 | 21 | // compatibility with CLI migraitons 22 | add_filter( 'wpsdb_cli_finalize_migration', array( $this, 'cli_migration' ), 10, 4 ); 23 | 24 | // internal AJAX handlers 25 | add_action( 'wp_ajax_wpsdbmf_determine_media_to_migrate', array( $this, 'ajax_determine_media_to_migrate' ) ); 26 | add_action( 'wp_ajax_wpsdbmf_migrate_media', array( $this, 'ajax_migrate_media' ) ); 27 | 28 | // external AJAX handlers 29 | add_action( 'wp_ajax_nopriv_wpsdbmf_get_remote_media_listing', array( $this, 'respond_to_get_remote_media_listing' ) ); 30 | add_action( 'wp_ajax_nopriv_wpsdbmf_push_request', array( $this, 'respond_to_push_request' ) ); 31 | add_action( 'wp_ajax_nopriv_wpsdbmf_remove_local_attachments', array( $this, 'respond_to_remove_local_attachments' ) ); 32 | } 33 | 34 | function get_local_attachments() { 35 | global $wpdb; 36 | $prefix = $wpdb->prefix; 37 | $temp_prefix = stripslashes( $_POST['temp_prefix'] ); 38 | 39 | /* 40 | * We determine which media files need migrating BEFORE the database migration is finalized. 41 | * Because of this we need to scan the *_post & *_postmeta that are prefixed using the temporary prefix. 42 | * Though this should only happen when we're responding to a get_remote_media_listing() call AND it's a push OR 43 | * we're scanning local files AND it's a pull. 44 | */ 45 | 46 | if( 47 | ( true == $this->responding_to_get_remote_media_listing && $_POST['intent'] == 'push' ) || 48 | ( false == $this->responding_to_get_remote_media_listing && $_POST['intent'] == 'pull' ) 49 | ) { 50 | 51 | $local_tables = array_flip( $this->get_tables() ); 52 | 53 | $posts_table_name = "{$temp_prefix}{$prefix}posts"; 54 | $postmeta_table_name = "{$temp_prefix}{$prefix}postmeta"; 55 | 56 | if( isset( $local_tables[$posts_table_name] ) && isset( $local_tables[$postmeta_table_name] ) ) { 57 | $prefix = $temp_prefix . $prefix; 58 | } 59 | 60 | } 61 | 62 | $local_attachments = $wpdb->get_results( 63 | "SELECT `{$prefix}posts`.`post_modified_gmt` AS 'date', pm1.`meta_value` AS 'file', pm2.`meta_value` AS 'metadata' 64 | FROM `{$prefix}posts` 65 | INNER JOIN `{$prefix}postmeta` pm1 ON `{$prefix}posts`.`ID` = pm1.`post_id` AND pm1.`meta_key` = '_wp_attached_file' 66 | LEFT OUTER JOIN `{$prefix}postmeta` pm2 ON `{$prefix}posts`.`ID` = pm2.`post_id` AND pm2.`meta_key` = '_wp_attachment_metadata' 67 | WHERE `{$prefix}posts`.`post_type` = 'attachment'", ARRAY_A 68 | ); 69 | 70 | if( is_multisite() ) { 71 | $blogs = $this->get_blogs(); 72 | $prefix = $wpdb->prefix; 73 | foreach( $blogs as $blog ) { 74 | $posts_table_name = "{$temp_prefix}{$prefix}{$blog}_posts"; 75 | $postmeta_table_name = "{$temp_prefix}{$prefix}{$blog}_postmeta"; 76 | if( isset( $local_tables[$posts_table_name] ) && isset( $local_tables[$postmeta_table_name] ) ) { 77 | $prefix = $temp_prefix . $prefix; 78 | } 79 | $attachments = $wpdb->get_results( 80 | "SELECT `{$prefix}{$blog}_posts`.`post_modified_gmt` AS 'date', pm1.`meta_value` AS 'file', pm2.`meta_value` AS 'metadata', {$blog} AS 'blog_id' 81 | FROM `{$prefix}{$blog}_posts` 82 | INNER JOIN `{$prefix}{$blog}_postmeta` pm1 ON `{$prefix}{$blog}_posts`.`ID` = pm1.`post_id` AND pm1.`meta_key` = '_wp_attached_file' 83 | LEFT OUTER JOIN `{$prefix}{$blog}_postmeta` pm2 ON `{$prefix}{$blog}_posts`.`ID` = pm2.`post_id` AND pm2.`meta_key` = '_wp_attachment_metadata' 84 | WHERE `{$prefix}{$blog}_posts`.`post_type` = 'attachment'", ARRAY_A 85 | ); 86 | 87 | $local_attachments = array_merge( $attachments, $local_attachments ); 88 | } 89 | } 90 | 91 | $local_attachments = array_map( array( $this, 'process_attachment_data' ), $local_attachments ); 92 | $local_attachments = array_filter( $local_attachments ); 93 | 94 | return $local_attachments; 95 | } 96 | 97 | function get_flat_attachments( $attachments ) { 98 | $flat_attachments = array(); 99 | foreach( $attachments as $attachment ) { 100 | $flat_attachments[] = $attachment['file']; 101 | if( isset( $attachment['sizes'] ) ) { 102 | $flat_attachments = array_merge( $flat_attachments, $attachment['sizes'] ); 103 | } 104 | } 105 | return $flat_attachments; 106 | } 107 | 108 | function process_attachment_data( $attachment ) { 109 | if ( isset( $attachment['blog_id'] ) ) { // used for multisite 110 | if ( defined( 'UPLOADBLOGSDIR' ) ) { 111 | $upload_dir = sprintf( '%s/files/', $attachment['blog_id'] ); 112 | } else { 113 | $upload_dir = sprintf( 'sites/%s/', $attachment['blog_id'] ); 114 | } 115 | $attachment['file'] = $upload_dir . $attachment['file']; 116 | } 117 | $upload_dir = str_replace( basename( $attachment['file'] ), '', $attachment['file'] ); 118 | if ( ! empty( $attachment['metadata'] ) ) { 119 | $attachment['metadata'] = @unserialize( $attachment['metadata'] ); 120 | if ( ! empty( $attachment['metadata']['sizes'] ) && is_array( $attachment['metadata']['sizes'] ) ) { 121 | foreach ( $attachment['metadata']['sizes'] as $size ) { 122 | if ( empty( $size['file'] ) ) continue; 123 | $attachment['sizes'][] = $upload_dir . $size['file']; 124 | } 125 | } 126 | } 127 | unset( $attachment['metadata'] ); 128 | return $attachment; 129 | } 130 | 131 | function uploads_dir() { 132 | if( defined( 'UPLOADBLOGSDIR' ) ) { 133 | $upload_dir = trailingslashit( ABSPATH ) . UPLOADBLOGSDIR; 134 | } 135 | else { 136 | $upload_dir = wp_upload_dir(); 137 | $upload_dir = $upload_dir['basedir']; 138 | } 139 | return trailingslashit( $upload_dir ); 140 | } 141 | 142 | function get_local_media() { 143 | $upload_dir = untrailingslashit( $this->uploads_dir() ); 144 | if( ! file_exists( $upload_dir ) ) return array(); 145 | 146 | $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $upload_dir ), RecursiveIteratorIterator::SELF_FIRST ); 147 | $local_media = array(); 148 | 149 | foreach( $files as $name => $object ){ 150 | $name = str_replace( array( $upload_dir . DS, '\\' ), array( '', '/' ), $name ); 151 | $local_media[$name] = $object->getSize(); 152 | } 153 | 154 | return $local_media; 155 | } 156 | 157 | function ajax_migrate_media() { 158 | $this->check_ajax_referer( 'migrate-media' ); 159 | $this->set_time_limit(); 160 | 161 | if ( $_POST['intent'] == 'pull' ) { 162 | $result = $this->process_pull_request(); 163 | return $result; 164 | } 165 | 166 | $result = $this->process_push_request(); 167 | return $result; 168 | } 169 | 170 | function process_pull_request() { 171 | $files_to_download = $_POST['file_chunk']; 172 | $remote_uploads_url = trailingslashit( $_POST['remote_uploads_url'] ); 173 | $parsed = parse_url( $_POST['url'] ); 174 | if( ! empty( $parsed['user'] ) ) { 175 | $credentials = sprintf( '%s:%s@', $parsed['user'], $parsed['pass'] ); 176 | $remote_uploads_url = str_replace( '://', '://' . $credentials, $remote_uploads_url ); 177 | } 178 | 179 | $upload_dir = $this->uploads_dir(); 180 | 181 | $errors = array(); 182 | foreach( $files_to_download as $file_to_download ) { 183 | $temp_file_path = $this->download_url( $remote_uploads_url . $file_to_download ); 184 | 185 | if( is_wp_error( $temp_file_path ) ) { 186 | $download_error = $temp_file_path->get_error_message(); 187 | $errors[] = __( sprintf( 'Could not download file: %1$s - %2$s', $remote_uploads_url . $file_to_download, $download_error ), 'wp-sync-db-media-files' ); 188 | continue; 189 | } 190 | 191 | $date = str_replace( basename( $file_to_download ), '', $file_to_download ); 192 | $new_path = $upload_dir . $date . basename( $file_to_download ); 193 | 194 | $move_result = @rename( $temp_file_path, $new_path ); 195 | 196 | if( false === $move_result ) { 197 | $folder = dirname( $new_path ); 198 | if( @file_exists( $folder ) ) { 199 | $errors[] = __( sprintf( 'Error attempting to move downloaded file. Temp path: %1$s - New Path: %2$s', $temp_file_path, $new_path ), 'wp-sync-db-media-files' ) . ' (#103mf)'; 200 | } 201 | else{ 202 | if( false === @mkdir( $folder, 0755, true ) ) { 203 | $errors[] = __( sprintf( 'Error attempting to create required directory: %s', $folder ), 'wp-sync-db-media-files' ) . ' (#104mf)'; 204 | } 205 | else { 206 | $move_result = @rename( $temp_file_path, $new_path ); 207 | if( false === $move_result ) { 208 | $errors[] = __( sprintf( 'Error attempting to move downloaded file. Temp path: %1$s - New Path: %2$s', $temp_file_path, $new_path ), 'wp-sync-db-media-files' ) . ' (#105mf)'; 209 | } 210 | } 211 | } 212 | } 213 | } 214 | 215 | if( ! empty( $errors ) ) { 216 | $return = array( 217 | 'wpsdb_error' => 1, 218 | 'body' => implode( '
', $errors ) . '
' 219 | ); 220 | $result = $this->end_ajax( json_encode( $return ) ); 221 | return $result; 222 | } 223 | 224 | // not required, just here because we have to return something otherwise the AJAX fails 225 | $return['success'] = 1; 226 | $result = $this->end_ajax( json_encode( $return ) ); 227 | return $result; 228 | } 229 | 230 | function process_push_request() { 231 | $files_to_migrate = $_POST['file_chunk']; 232 | 233 | $upload_dir = $this->uploads_dir(); 234 | 235 | $body = ''; 236 | foreach( $files_to_migrate as $file_to_migrate ) { 237 | $body .= $this->file_to_multipart( $upload_dir . $file_to_migrate ); 238 | } 239 | 240 | $post_args = array( 241 | 'action' => 'wpsdbmf_push_request', 242 | 'files' => serialize( $files_to_migrate ) 243 | ); 244 | 245 | $post_args['sig'] = $this->create_signature( $post_args, $_POST['key'] ); 246 | 247 | $body .= $this->array_to_multipart( $post_args ); 248 | 249 | $args['body'] = $body; 250 | $ajax_url = trailingslashit( $_POST['url'] ) . 'wp-admin/admin-ajax.php'; 251 | $response = $this->remote_post( $ajax_url, '', __FUNCTION__, $args ); 252 | $response = $this->verify_remote_post_response( $response ); 253 | 254 | $result = $this->end_ajax( json_encode( $response ) ); 255 | return $result; 256 | } 257 | 258 | function respond_to_push_request() { 259 | $filtered_post = $this->filter_post_elements( $_POST, array( 'action', 'files' ) ); 260 | $filtered_post['files'] = stripslashes( $filtered_post['files'] ); 261 | if ( ! $this->verify_signature( $filtered_post, $this->settings['key'] ) ) { 262 | $return = array( 263 | 'wpsdb_error' => 1, 264 | 'body' => $this->invalid_content_verification_error . ' (#101mf)', 265 | ); 266 | $result = $this->end_ajax( serialize( $return ) ); 267 | return $result; 268 | } 269 | 270 | if( ! isset( $_FILES['media'] ) ) { 271 | $return = array( 272 | 'wpsdb_error' => 1, 273 | 'body' => __( '$_FILES is empty, the upload appears to have failed', 'wp-sync-db-media-files' ) . ' (#106mf)', 274 | ); 275 | $result = $this->end_ajax( serialize( $return ) ); 276 | return $result; 277 | } 278 | 279 | $upload_dir = $this->uploads_dir(); 280 | 281 | $files = $this->diverse_array( $_FILES['media'] ); 282 | $file_paths = unserialize( $filtered_post['files'] ); 283 | $i = 0; 284 | $errors = array(); 285 | foreach( $files as &$file ) { 286 | $destination = $upload_dir . $file_paths[$i]; 287 | $folder = dirname( $destination ); 288 | 289 | if( false === @file_exists( $folder ) && false === @mkdir( $folder, 0755, true ) ) { 290 | $errors[] = __( sprintf( 'Error attempting to create required directory: %s', $folder ), 'wp-sync-db-media-files' ) . ' (#108mf)'; 291 | ++$i; 292 | continue; 293 | } 294 | 295 | if( false === @move_uploaded_file( $file['tmp_name'], $destination ) ) { 296 | $errors[] = __( sprintf( 'A problem occurred when attempting to move the temp file "%1$s" to "%2$s"', $file['tmp_name'], $destination ), 'wp-sync-db-media-files' ) . ' (#107mf)'; 297 | } 298 | ++$i; 299 | } 300 | 301 | $return = array( 'success' => 1 ); 302 | if( ! empty( $errors ) ) { 303 | $return = array( 304 | 'wpsdb_error' => 1, 305 | 'body' => implode( '
', $errors ) . '
' 306 | ); 307 | } 308 | $result = $this->end_ajax( serialize( $return ) ); 309 | return $result; 310 | } 311 | 312 | function ajax_determine_media_to_migrate() { 313 | $this->check_ajax_referer( 'determine-media-to-migrate' ); 314 | $this->set_time_limit(); 315 | 316 | $local_attachments = $this->get_local_attachments(); 317 | $local_media = $this->get_local_media(); 318 | 319 | $data = array(); 320 | $data['action'] = 'wpsdbmf_get_remote_media_listing'; 321 | $data['temp_prefix'] = $this->temp_prefix; 322 | $data['intent'] = $_POST['intent']; 323 | $data['sig'] = $this->create_signature( $data, $_POST['key'] ); 324 | $ajax_url = trailingslashit( $_POST['url'] ) . 'wp-admin/admin-ajax.php'; 325 | $response = $this->remote_post( $ajax_url, $data, __FUNCTION__ ); 326 | $response = $this->verify_remote_post_response( $response ); 327 | 328 | $upload_dir = $this->uploads_dir(); 329 | 330 | $remote_attachments = $response['remote_attachments']; 331 | $remote_media = $response['remote_media']; 332 | 333 | $this->files_to_migrate = array(); 334 | 335 | if( $_POST['intent'] == 'pull' ) { 336 | $this->media_diff( $local_attachments, $remote_attachments, $local_media, $remote_media ); 337 | } 338 | else { 339 | $this->media_diff( $remote_attachments, $local_attachments, $remote_media, $local_media ); 340 | } 341 | 342 | $return['files_to_migrate'] = $this->files_to_migrate; 343 | $return['total_size'] = array_sum( $this->files_to_migrate ); 344 | $return['remote_uploads_url'] = $response['remote_uploads_url']; 345 | 346 | // remove local/remote media if it doesn't exist on the local/remote site 347 | if( $_POST['remove_local_media'] == '1' ) { 348 | if( $_POST['intent'] == 'pull' ) { 349 | $this->remove_local_attachments( $remote_attachments ); 350 | } 351 | else { 352 | $data = array(); 353 | $data['action'] = 'wpsdbmf_remove_local_attachments'; 354 | $data['remote_attachments'] = serialize( $local_attachments ); 355 | $data['sig'] = $this->create_signature( $data, $_POST['key'] ); 356 | $ajax_url = trailingslashit( $_POST['url'] ) . 'wp-admin/admin-ajax.php'; 357 | $response = $this->remote_post( $ajax_url, $data, __FUNCTION__ ); 358 | // the response is ignored here (for now) as this is not a critical task 359 | } 360 | } 361 | 362 | $result = $this->end_ajax( json_encode( $return ) ); 363 | return $result; 364 | } 365 | 366 | function respond_to_remove_local_attachments() { 367 | $filtered_post = $this->filter_post_elements( $_POST, array( 'action', 'remote_attachments' ) ); 368 | $filtered_post['remote_attachments'] = stripslashes( $filtered_post['remote_attachments'] ); 369 | if ( ! $this->verify_signature( $filtered_post, $this->settings['key'] ) ) { 370 | $return = array( 371 | 'wpsdb_error' => 1, 372 | 'body' => $this->invalid_content_verification_error . ' (#109mf)', 373 | ); 374 | $result = $this->end_ajax( serialize( $return ) ); 375 | return $result; 376 | } 377 | 378 | $remote_attachments = @unserialize( $filtered_post['remote_attachments'] ); 379 | if( false === $remote_attachments ) { 380 | $return = array( 381 | 'wpsdb_error' => 1, 382 | 'body' => __( 'Error attempting to unserialize the remote attachment data', 'wp-sync-db-media-files' ) . ' (#110mf)', 383 | ); 384 | $result = $this->end_ajax( serialize( $return ) ); 385 | return $result; 386 | } 387 | 388 | $this->remove_local_attachments( $remote_attachments ); 389 | 390 | $return = array( 391 | 'success' => 1, 392 | ); 393 | $result = serialize( json_encode( $return ) ); 394 | return $result; 395 | } 396 | 397 | function remove_local_attachments( $remote_attachments ) { 398 | $flat_remote_attachments = array_flip( $this->get_flat_attachments( $remote_attachments ) ); 399 | $local_media = $this->get_local_media(); 400 | // remove local media if it doesn't exist on the remote site 401 | $temp_local_media = array_keys( $local_media ); 402 | $allowed_mime_types = array_flip( get_allowed_mime_types() ); 403 | $upload_dir = $this->uploads_dir(); 404 | foreach( $temp_local_media as $local_media_file ) { 405 | // don't remove folders 406 | if( false === is_file( $upload_dir . $local_media_file ) ) continue; 407 | $filetype = wp_check_filetype( $local_media_file ); 408 | // don't remove files that we shouldn't remove, e.g. .php, .sql, etc 409 | if( false === isset( $allowed_mime_types[$filetype['type']] ) ) continue; 410 | // don't remove files that exist on the remote site 411 | if( true === isset( $flat_remote_attachments[$local_media_file] ) ) continue; 412 | 413 | @unlink( $upload_dir . $local_media_file ); 414 | } 415 | } 416 | 417 | function media_diff( $site_a_attachments, $site_b_attachments, $site_a_media, $site_b_media ) { 418 | foreach( $site_b_attachments as $attachment ) { 419 | $local_attachment_key = $this->multidimensional_search( array( 'file' => $attachment['file'] ), $site_a_attachments ); 420 | if( false === $local_attachment_key ) continue; 421 | $remote_timestamp = strtotime( $attachment['date'] ); 422 | $local_timestamp = strtotime( $site_a_attachments[$local_attachment_key]['date'] ); 423 | if( $local_timestamp >= $remote_timestamp ) { 424 | if( ! isset( $site_a_media[$attachment['file']] ) ) { 425 | $this->add_files_to_migrate( $attachment, $site_b_media ); 426 | } 427 | else { 428 | $this->maybe_add_resized_images( $attachment, $site_b_media, $site_a_media ); 429 | } 430 | } 431 | else { 432 | $this->add_files_to_migrate( $attachment, $site_b_media ); 433 | } 434 | } 435 | } 436 | 437 | function add_files_to_migrate( $attachment, $remote_media ) { 438 | if( isset( $remote_media[$attachment['file']] ) ) { 439 | $this->files_to_migrate[$attachment['file']] = $remote_media[$attachment['file']]; 440 | } 441 | if( empty( $attachment['sizes'] ) || apply_filters( 'wpsdb_exclude_resized_media', false ) ) return; 442 | foreach( $attachment['sizes'] as $size ) { 443 | if( isset( $remote_media[$size] ) ) { 444 | $this->files_to_migrate[$size] = $remote_media[$size]; 445 | } 446 | } 447 | } 448 | 449 | function maybe_add_resized_images( $attachment, $site_b_media, $site_a_media ) { 450 | if( empty( $attachment['sizes'] ) || apply_filters( 'wpsdb_exclude_resized_media', false ) ) return; 451 | foreach( $attachment['sizes'] as $size ) { 452 | if( isset( $site_b_media[$size] ) && ! isset( $site_a_media[$size] ) ) { 453 | $this->files_to_migrate[$size] = $site_b_media[$size]; 454 | } 455 | } 456 | } 457 | 458 | function respond_to_get_remote_media_listing() { 459 | $filtered_post = $this->filter_post_elements( $_POST, array( 'action', 'temp_prefix', 'intent' ) ); 460 | if ( ! $this->verify_signature( $filtered_post, $this->settings['key'] ) ) { 461 | $return = array( 462 | 'wpsdb_error' => 1, 463 | 'body' => $this->invalid_content_verification_error . ' (#100mf)', 464 | ); 465 | $result = $this->end_ajax( serialize( $return ) ); 466 | return $result; 467 | } 468 | 469 | if( defined( 'UPLOADBLOGSDIR' ) ) { 470 | $upload_url = home_url( UPLOADBLOGSDIR ); 471 | } 472 | else { 473 | $upload_dir = wp_upload_dir(); 474 | $upload_url = $upload_dir['baseurl']; 475 | } 476 | 477 | $this->responding_to_get_remote_media_listing = true; 478 | 479 | $return['remote_attachments'] = $this->get_local_attachments(); 480 | $return['remote_media'] = $this->get_local_media(); 481 | $return['remote_uploads_url'] = $upload_url; 482 | 483 | $result = $this->end_ajax( serialize( $return ) ); 484 | return $result; 485 | } 486 | 487 | function migration_form_controls() { 488 | $this->template( 'migrate' ); 489 | } 490 | 491 | function accepted_profile_fields( $profile_fields ) { 492 | $profile_fields[] = 'media_files'; 493 | $profile_fields[] = 'remove_local_media'; 494 | return $profile_fields; 495 | } 496 | 497 | function load_assets() { 498 | $plugins_url = trailingslashit( plugins_url() ) . trailingslashit( $this->plugin_folder_name ); 499 | $src = $plugins_url . 'asset/js/script.js'; 500 | $version = defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? time() : $this->plugin_version; 501 | wp_enqueue_script( 'wp-sync-db-media-files-script', $src, array( 'jquery', 'wp-sync-db-common', 'wp-sync-db-hook', 'wp-sync-db-script' ), $version, true ); 502 | 503 | wp_localize_script( 'wp-sync-db-media-files-script', 'wpsdbmf_strings', array( 504 | 'determining' => __( "Determining which media files to migrate, please wait...", 'wp-sync-db-media-files' ), 505 | 'error_determining' => __( "Error while attempting to determine which media files to migrate.", 'wp-sync-db-media-files' ), 506 | 'migration_failed' => __( "Migration failed", 'wp-sync-db-media-files' ), 507 | 'problem_migrating_media' => __( "A problem occurred when migrating the media files.", 'wp-sync-db-media-files' ), 508 | 'media_files' => __( "Media Files", 'wp-sync-db-media-files' ), 509 | 'migrating_media_files' => __( "Migrating media files", 'wp-sync-db-media-files' ), 510 | ) ); 511 | 512 | } 513 | 514 | function establish_remote_connection_data( $data ) { 515 | $data['media_files_available'] = '1'; 516 | $data['media_files_version'] = $this->plugin_version; 517 | if( function_exists( 'ini_get' ) ) { 518 | $max_file_uploads = ini_get( 'max_file_uploads' ); 519 | } 520 | $max_file_uploads = ( empty( $max_file_uploads ) ) ? 20 : $max_file_uploads; 521 | $data['media_files_max_file_uploads'] = apply_filters( 'wpsdbmf_max_file_uploads', $max_file_uploads ); 522 | return $data; 523 | } 524 | 525 | function multidimensional_search( $needle, $haystack ) { 526 | if( empty( $needle ) || empty( $haystack ) ) return false; 527 | 528 | foreach( $haystack as $key => $value ) { 529 | foreach ( $needle as $skey => $svalue ) { 530 | $exists = ( isset( $haystack[$key][$skey] ) && $haystack[$key][$skey] === $svalue ); 531 | } 532 | if( $exists ) return $key; 533 | } 534 | 535 | return false; 536 | } 537 | 538 | function get_blogs() { 539 | global $wpdb; 540 | 541 | $blogs = $wpdb->get_results( 542 | "SELECT blog_id 543 | FROM {$wpdb->blogs} 544 | WHERE spam = '0' 545 | AND deleted = '0' 546 | AND archived = '0' 547 | AND blog_id != 1 548 | "); 549 | 550 | $clean_blogs = array(); 551 | foreach( $blogs as $blog ) { 552 | $clean_blogs[] = $blog->blog_id; 553 | } 554 | 555 | return $clean_blogs; 556 | } 557 | 558 | function download_url( $url, $timeout = 300 ) { 559 | //WARNING: The file is not automatically deleted, The script must unlink() the file. 560 | if ( ! $url ) 561 | return new WP_Error('http_no_url', __('Invalid URL Provided.')); 562 | 563 | $tmpfname = wp_tempnam($url); 564 | if ( ! $tmpfname ) 565 | return new WP_Error('http_no_file', __('Could not create Temporary file.')); 566 | 567 | $response = wp_remote_get( $url, array( 'timeout' => $timeout, 'stream' => true, 'filename' => $tmpfname, 'reject_unsafe_urls' => false ) ); 568 | 569 | if ( is_wp_error( $response ) ) { 570 | unlink( $tmpfname ); 571 | return $response; 572 | } 573 | 574 | if ( 200 != wp_remote_retrieve_response_code( $response ) ){ 575 | unlink( $tmpfname ); 576 | return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ) ); 577 | } 578 | 579 | return $tmpfname; 580 | } 581 | 582 | function js_variables() { 583 | ?> 584 | var wpsdb_media_files_version = 'plugin_version; ?>'; 585 | 1, 'body' => $this->error ); 591 | $result = $this->end_ajax( json_encode( $return ) ); 592 | return $result; 593 | } 594 | 595 | if ( ! is_serialized( trim( $response ) ) ) { 596 | $return = array( 'wpsdb_error' => 1, 'body' => $response ); 597 | $result = $this->end_ajax( json_encode( $return ) ); 598 | return $result; 599 | } 600 | 601 | $response = unserialize( trim( $response ) ); 602 | 603 | if ( isset( $response['wpsdb_error'] ) ) { 604 | $result = $this->end_ajax( json_encode( $response ) ); 605 | return $result; 606 | } 607 | return $response; 608 | } 609 | 610 | function add_nonces( $nonces ) { 611 | $nonces['migrate_media'] = wp_create_nonce( 'migrate-media' ); 612 | $nonces['determine_media_to_migrate'] = wp_create_nonce( 'determine-media-to-migrate' ); 613 | return $nonces; 614 | } 615 | 616 | function cli_migration( $outcome, $profile, $verify_connection_response, $initiate_migration_response ) { 617 | global $wpsdb, $wpsdb_cli; 618 | if ( true !== $outcome ) return $outcome; 619 | if ( !isset( $profile['media_files'] ) || '1' !== $profile['media_files'] ) return $outcome; 620 | 621 | if ( !isset( $verify_connection_response['media_files_max_file_uploads'] ) ) { 622 | return $wpsdb_cli->cli_error( __( 'WP Sync DB Media Files does not seems to be installed/active on the remote website.', 'wp-sync-db-media-files' ) ); 623 | } 624 | 625 | $this->set_time_limit(); 626 | $wpsdb->set_cli_migration(); 627 | $this->set_cli_migration(); 628 | 629 | $connection_info = explode( "\n", $profile['connection_info'] ); 630 | 631 | $_POST['intent'] = $profile['action']; 632 | $_POST['url'] = trim( $connection_info[0] ); 633 | $_POST['key'] = trim( $connection_info[1] ); 634 | $_POST['remove_local_media'] = ( isset( $profile['remove_local_media'] ) ) ? 1 : 0; 635 | $_POST['temp_prefix'] = $verify_connection_response['temp_prefix']; 636 | 637 | do_action( 'wpsdb_cli_before_determine_media_to_migrate', $profile, $verify_connection_response, $initiate_migration_response ); 638 | 639 | $response = $this->ajax_determine_media_to_migrate(); 640 | if( is_wp_error( $determine_media_to_migrate_response = $wpsdb_cli->verify_cli_response( $response, 'ajax_determine_media_to_migrate()' ) ) ) return $determine_media_to_migrate_response; 641 | 642 | $remote_uploads_url = $determine_media_to_migrate_response['remote_uploads_url']; 643 | $files_to_migrate = $determine_media_to_migrate_response['files_to_migrate']; 644 | // seems like this value needs to be different depending on pull/push? 645 | $bottleneck = $wpsdb->get_bottleneck(); 646 | 647 | while ( !empty( $files_to_migrate ) ) { 648 | $file_chunk_to_migrate = array(); 649 | $file_chunk_size = 0; 650 | $number_of_files_to_migrate = 0; 651 | foreach ( $files_to_migrate as $file_to_migrate => $file_size ) { 652 | if ( empty( $file_chunk_to_migrate ) ) { 653 | $file_chunk_to_migrate[] = $file_to_migrate; 654 | $file_chunk_size += $file_size; 655 | unset( $files_to_migrate[$file_to_migrate] ); 656 | ++$number_of_files_to_migrate; 657 | } else { 658 | if ( ( $file_chunk_size + $file_size ) > $bottleneck || $number_of_files_to_migrate >= $verify_connection_response['media_files_max_file_uploads'] ) { 659 | break; 660 | } else { 661 | $file_chunk_to_migrate[] = $file_to_migrate; 662 | $file_chunk_size += $file_size; 663 | unset( $files_to_migrate[$file_to_migrate] ); 664 | ++$number_of_files_to_migrate; 665 | } 666 | } 667 | 668 | $_POST['file_chunk'] = $file_chunk_to_migrate; 669 | $_POST['remote_uploads_url'] = $remote_uploads_url; 670 | 671 | $response = $this->ajax_migrate_media(); 672 | if( is_wp_error( $migrate_media_response = $wpsdb_cli->verify_cli_response( $response, 'ajax_migrate_media()' ) ) ) return $migrate_media_response; 673 | } 674 | } 675 | return true; 676 | } 677 | } 678 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-sync-db/wp-sync-db-media-files", 3 | "type": "wordpress-plugin", 4 | "homepage": "https://github.com/wp-sync-db/wp-sync-db-media-files", 5 | "license": "GPL-2.0", 6 | "description": "WP Sync DB Media File Addon for WP Sync DB", 7 | "keywords": ["plugin","wordpress","wp-sync-db","media"], 8 | "require": { 9 | "composer/installers": "~1.0.6" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /languages/wp-migrate-db-pro-media-files-en.pot: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: WP Sync DB Media Files\n" 4 | "POT-Creation-Date: 2014-05-28 13:46+1000\n" 5 | "PO-Revision-Date: 2014-05-28 13:46+1000\n" 6 | "Last-Translator: Delicious Brains \n" 7 | "Language-Team: Delicious Brains \n" 8 | "Language: en\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 1.6.5\n" 13 | "X-Poedit-Basepath: ..\n" 14 | "X-Poedit-SourceCharset: UTF-8\n" 15 | "X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;esc_attr__;" 16 | "esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c;_n_noop:1,2;" 17 | "_nx_noop:3c,1,2;__ngettext_noop:1,2\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | "X-Poedit-SearchPath-0: .\n" 20 | 21 | #: class/wpsdb-media-files.php:273 22 | msgid "$_FILES is empty, the upload appears to have failed" 23 | msgstr "" 24 | 25 | #: class/wpsdb-media-files.php:382 26 | msgid "Error attempting to unserialize the remote attachment data" 27 | msgstr "" 28 | 29 | #: class/wpsdb-media-files.php:504 30 | msgid "Determining which media files to migrate, please wait..." 31 | msgstr "" 32 | 33 | #: class/wpsdb-media-files.php:505 34 | msgid "Error while attempting to determine which media files to migrate." 35 | msgstr "" 36 | 37 | #: class/wpsdb-media-files.php:506 38 | msgid "Migration failed" 39 | msgstr "" 40 | 41 | #: class/wpsdb-media-files.php:507 42 | msgid "A problem occurred when migrating the media files." 43 | msgstr "" 44 | 45 | #: class/wpsdb-media-files.php:508 template/migrate.php:6 46 | msgid "Media Files" 47 | msgstr "" 48 | 49 | #: class/wpsdb-media-files.php:509 50 | msgid "Migrating media files" 51 | msgstr "" 52 | 53 | #: class/wpsdb-media-files.php:561 54 | msgid "Invalid URL Provided." 55 | msgstr "" 56 | 57 | #: class/wpsdb-media-files.php:565 58 | msgid "Could not create Temporary file." 59 | msgstr "" 60 | 61 | #: class/wpsdb-media-files.php:623 62 | msgid "" 63 | "WP Sync DB Media Files does not seems to be installed/active on the " 64 | "remote website." 65 | msgstr "" 66 | 67 | #: template/migrate.php:15 68 | msgid "" 69 | "Remove local media files that are not " 70 | "found on the remote site" 71 | msgstr "" 72 | 73 | #: template/migrate.php:23 74 | msgid "Addon Missing" 75 | msgstr "" 76 | 77 | #: template/migrate.php:23 78 | msgid "" 79 | "The Media Files addon is inactive on the remote site. " 80 | "Please install and activate it to enable media file migration." 81 | msgstr "" 82 | 83 | #: template/migrate.php:27 84 | msgid "Version Mismatch" 85 | msgstr "" 86 | -------------------------------------------------------------------------------- /template/migrate.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 7 |
8 |
    9 |
  • 10 | 14 |
  • 15 |
16 |
17 | 20 | 23 |
24 | -------------------------------------------------------------------------------- /version.php: -------------------------------------------------------------------------------- 1 |