├── MigratorAbstract.php ├── README.md ├── LICENSE └── ProcessMigrator.module /MigratorAbstract.php: -------------------------------------------------------------------------------- 1 | Site > Add New and in the "Add Module from URL" option, enter: 14 | https://github.com/adrianbj/ProcessMigrator/archive/master.zip 15 | 16 | 17 | ## Usage 18 | Go to the Setup Page > Migrator and follow the prompts. 19 | 20 | ## What it can migrate 21 | 22 | Fields, templates, and page content for all field types including: 23 | * All standard field types, including RTE, and decoding of links modified by the PageLinkAbstractor module and abstracting again on the destination PW install. 24 | * File/Image/CropImage fields including the actual files/images/thumbnails, and all other variations 25 | * All Profields field types: Table, PageTable, Multiplier, and Texareas 26 | * All? custom field types 27 | * Repeaters fields and all their required fields, templates, and content including files/images 28 | * Page fields (and the pages, templates, and fields that make up their selectable pages) 29 | * Multilanguage versions of field content, page names and page titles 30 | * Templates (including Access, Family, URL and other settings) and the template .php files. It even grabs the appropriate file if you are using the "Alternate Template Filename" setting. NB the templates directory on the destination PW installation must be writable for these to be imported. 31 | 32 | Files/images/template files and the json structure/data file are exported in a zip file which is then imported into the destination PW install. 33 | 34 | So, you could build sections of content on a local dev PW installation, export it, and then with a couple of clicks import everything into the live PW installation. 35 | 36 | ## Notes 37 | * It supports multi-language fields, but you should make sure to have language support already installed on the destination installation before running the import 38 | * It supports links in RTE fields that have been converted with the PageLinkAbstractor module - make sure the module is installed on the destination server before running the import 39 | 40 | 41 | ## Outstanding Issues 42 | Some outstanding issues that I hope to get to shortly: 43 | * Need to support images inserted from a different page into an RTE field 44 | * Rewrite any references to page ids, eg $pages->get(xxxx) in template .php files so they will be converted to the correct id on the destination installation. 45 | * Need to look into the new core link abstractor that was added to PW 2.4 and see how to handle those links compared to the PageLinkAbstractor module. 46 | * Need to add checks so that existing template php files are not overwritten (or give the option to choose) 47 | * Might need to override PHP max_execution_time and other settings for larger exports and maybe chunk out zipping of all images to prevent memory issues on larger exports. 48 | 49 | 50 | ## Support 51 | https://processwire.com/talk/topic/8660-migrator 52 | 53 | 54 | ## License 55 | 56 | This program is free software; you can redistribute it and/or 57 | modify it under the terms of the GNU General Public License 58 | as published by the Free Software Foundation; either version 2 59 | of the License, or (at your option) any later version. 60 | 61 | This program is distributed in the hope that it will be useful, 62 | but WITHOUT ANY WARRANTY; without even the implied warranty of 63 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 64 | GNU General Public License for more details. 65 | 66 | You should have received a copy of the GNU General Public License 67 | along with this program; if not, write to the Free Software 68 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 69 | 70 | (See included LICENSE file for full license text.) -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /ProcessMigrator.module: -------------------------------------------------------------------------------- 1 | 'Migrator', 32 | 'version' => '0.7.8', 33 | 'summary' => 'Automatically migrate content from one PW installation to another. Also allows 3rd party modules to convert content from other sources.', 34 | 'href' => 'https://processwire.com/talk/topic/4420-migrator/', 35 | 'singular' => true, 36 | 'autoload' => false, 37 | 'icon' => 'exchange', 38 | 'nav' => array( 39 | array( 40 | 'url' => 'export/', 41 | 'label' => 'Export', 42 | 'icon' => 'arrow-right', 43 | ), 44 | array( 45 | 'url' => 'import/', 46 | 'label' => 'Import', 47 | 'icon' => 'arrow-left' 48 | ), 49 | array( 50 | 'url' => 'restore/', 51 | 'label' => 'Restore', 52 | 'icon' => 'reply' 53 | ) 54 | ) 55 | ); 56 | } 57 | 58 | /** 59 | * Name used for the page created in the admin 60 | * 61 | */ 62 | const adminPageName = 'migrator'; 63 | 64 | 65 | protected $pageFiles = array(); 66 | protected $repeaterSubFields = array(); 67 | protected $templateFiles = array(); 68 | protected $migratedTemplateFileNames = array(); 69 | protected $selectedFields = array(); 70 | protected $selectedPages = array(); 71 | 72 | protected $zipFilename = ''; 73 | //protected $jsonFilename = ''; 74 | //protected $migratorFilesDir = ''; 75 | protected $newPage = ''; 76 | protected $newField = ''; 77 | protected $base_url = ''; 78 | protected $thumb_suffix = ''; 79 | 80 | /** 81 | * Instance of Template, used for imported pages 82 | * 83 | */ 84 | protected $template = null; 85 | 86 | /** 87 | * Instance of Page, representing the parent Page for imported pages 88 | * 89 | */ 90 | protected $parent = null; 91 | 92 | 93 | /** 94 | * Default configuration for module 95 | * 96 | */ 97 | static public function getDefaultData() { 98 | return array( 99 | "ignoredSubFolders" => "" 100 | ); 101 | } 102 | 103 | /** 104 | * Populate the default config data 105 | * 106 | */ 107 | public function __construct() { 108 | foreach(self::getDefaultData() as $key => $value) { 109 | $this->$key = $value; 110 | } 111 | } 112 | 113 | 114 | /** 115 | * Initialize the module 116 | * 117 | */ 118 | public function init() { 119 | parent::init(); 120 | ini_set('auto_detect_line_endings', true); 121 | wire("config")->scripts->add("/wire/modules/Inputfield/InputfieldDatetime/jquery-ui-timepicker-addon.js"); 122 | } 123 | 124 | /** 125 | * Executed when root url for module is accessed 126 | * 127 | */ 128 | public function ___execute() { 129 | 130 | $form = $this->buildForm1(); 131 | if($this->input->post->submit) { 132 | if($this->processForm1($form) || $this->processExportForm2($form) || $this->processImportForm2($form)) $this->session->redirect('./'.$this->session->type.'/'); 133 | } 134 | return $form->render(); 135 | } 136 | 137 | /** 138 | * Executed when ./export/ url for module is accessed 139 | * 140 | */ 141 | public function ___executeExport() { 142 | 143 | $form = $this->buildExportForm2(); 144 | if($this->input->post->submit) { 145 | return $this->processExportForm2($form); 146 | } else { 147 | //$form = $this->buildExportForm2(); 148 | return $form->render(); 149 | } 150 | } 151 | 152 | 153 | /** 154 | * Executed when ./import/ url for module is accessed 155 | * 156 | */ 157 | public function ___executeImport() { 158 | 159 | $form = $this->buildImportForm2(); 160 | 161 | if($this->input->post->submit) { 162 | return $this->processImportForm2($form); 163 | } else { 164 | return $form->render(); 165 | } 166 | 167 | } 168 | 169 | 170 | /** 171 | * Executed when ./restore/ url for module is accessed 172 | * 173 | */ 174 | public function ___executeRestore() { 175 | 176 | $form = $this->buildRestoreForm2(); 177 | 178 | if($this->input->post->submit) { 179 | return $this->processRestoreForm2($form); 180 | } else { 181 | return $form->render(); 182 | } 183 | 184 | } 185 | 186 | 187 | 188 | 189 | /** 190 | * Build the "Step 1" form 191 | * 192 | */ 193 | protected function buildForm1() { 194 | 195 | $form = $this->modules->get("InputfieldForm"); 196 | $form->method = 'post'; 197 | $form->description = "Step 1: Export, Import, or Restore"; 198 | 199 | $f = $this->modules->get("InputfieldSelect"); 200 | $f->name = 'type'; 201 | $f->label = 'Export, Import, or Restore'; 202 | $f->required = true; 203 | $f->addOption(''); 204 | $f->addOption('export', 'Export'); 205 | $f->addOption('import', 'Import'); 206 | if(class_exists('WireDatabaseBackup')){ //not present in older version of PW - prior to Aug 19, 2014 (approx 2.4.13) 207 | $f->addOption('restore', 'Restore'); 208 | } 209 | if($this->session->type) $f->attr('value', $this->session->type); 210 | $form->add($f); 211 | 212 | $this->addSubmit($form, 'Continue to Step 2'); 213 | 214 | return $form; 215 | } 216 | 217 | 218 | 219 | /** 220 | * Process the "Step 1" form and populate session variables with the results 221 | * 222 | */ 223 | protected function processForm1(InputfieldForm $form) { 224 | 225 | $form->processInput($this->input->post); 226 | if(count($form->getErrors())) return false; 227 | 228 | //$this->session->type = (int) $form->get('type')->value; 229 | 230 | $type = $form->get('type')->value; 231 | 232 | if(!$type) { 233 | $this->error("Missing required Export/Import/Restore action type"); 234 | return false; 235 | } 236 | 237 | $this->session->type = $type; 238 | return true; 239 | } 240 | 241 | 242 | 243 | 244 | /** 245 | * Build the "Export Step 2" form 246 | * 247 | */ 248 | protected function buildExportForm2() { 249 | 250 | $form = $this->modules->get("InputfieldForm"); 251 | $form->method = 'post'; 252 | $form->description = "Step 2: Export Page Tree"; 253 | 254 | $f = $this->modules->get("InputfieldPageListSelect"); 255 | $f->name = 'treeParent'; 256 | $f->label = 'Parent Page'; 257 | $f->required = true; 258 | $f->description = "The parent of the page tree you want to export."; 259 | if($this->session->treeParent) $f->attr('value', $this->session->treeParent); 260 | $form->add($f); 261 | 262 | $f = $this->modules->get("InputfieldSelect"); 263 | $f->name = 'export_components'; 264 | $f->label = 'Components to export'; 265 | $f->required = true; 266 | $f->addOption('everything', 'Everything, including all data pages'); 267 | $f->addOption('fields_templates_and_structural_pages', 'Fields, Templates and Structural Pages'); 268 | $f->addOption('fields_and_templates_only', 'Fields and Templates Only'); 269 | if($this->session->export_components) $f->attr('value', $this->session->export_components); 270 | $form->add($f); 271 | 272 | $f = $this->modules->get("InputfieldDatetime"); 273 | $f->label = "Changes since"; 274 | $f->datepicker = 3; 275 | $f->attr("name+id", "changes_since"); 276 | $f->dateInputFormat = "Y-m-d"; 277 | $f->timeInputFormat = "H:i:s"; 278 | if($this->session->changes_since){ 279 | $f->attr('value', $this->session->changes_since); 280 | $f->attr('data-ts', $this->session->changes_since); 281 | } 282 | $f->description = "You can use this to export all pages that have changed since this date/time."; 283 | $f->collapsed = Inputfield::collapsedBlank; 284 | $form->add($f); 285 | 286 | $f = $this->modules->get("InputfieldSelect"); 287 | $f->name = 'save_or_copy'; 288 | $f->label = 'Output Format'; 289 | $f->description = "SAVE zip file to your computer OR display code so you can COPY and then paste into new site.\nNB Copy will not work for migrating full page content if there are included files/images. It also won't migrate required template files."; 290 | $f->required = true; 291 | $f->addOption('save', 'Save'); 292 | $f->addOption('copy', 'Copy'); 293 | if($this->session->save_or_copy) $f->attr('value', $this->session->save_or_copy); 294 | $form->add($f); 295 | 296 | 297 | $f = $this->modules->get("InputfieldAsmSelect"); 298 | $f->name = 'helper_files'; 299 | $f->label = 'Helper Files'; 300 | $f->description = "Determines which additional helper files (inc, css, js etc) will be included in the export. These will be in addition to the required template php files needed for the templates, which will automatically be included."; 301 | $f->showIf = "save_or_copy=save"; 302 | 303 | /* 304 | foreach($iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->config->paths->templates, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST) as $item){ 305 | if ($item->isDir()) { 306 | //$f->addOption($iterator->getSubPathName(), $iterator->getSubPathName()); 307 | } 308 | else { 309 | $exclude = false; 310 | foreach(explode("\n", $this->data['ignoredSubFolders']) as $ignoredSubFolder) { 311 | if(pathinfo($iterator->getSubPathName(), PATHINFO_DIRNAME) == $ignoredSubFolder) $exclude = true; 312 | } 313 | if(!$exclude) $f->addOption($iterator->getSubPathName(), $iterator->getSubPathName()); 314 | } 315 | } 316 | */ 317 | 318 | // fix by @jlahijani: prevent export from completely erroring if on windows and have directories that exceed the path limit (like 'node_modules'), which ignoredSubfolders won't have an affect on 319 | try { 320 | foreach($iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->config->paths->templates, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST) as $item){ 321 | if ($item->isDir()) { 322 | //$f->addOption($iterator->getSubPathName(), $iterator->getSubPathName()); 323 | } 324 | else { 325 | $exclude = false; 326 | foreach(explode("\n", $this->data['ignoredSubFolders']) as $ignoredSubFolder) { 327 | if(dirname(pathinfo($iterator->getSubPathName(), PATHINFO_DIRNAME)) == $ignoredSubFolder) $exclude = true; 328 | // @jlahijani: dirty way to detect ignored folders if on windows and hit directory path bug 329 | //if( strpos($iterator->getSubPathName(), $ignoredSubFolder ) !== false ) $exclude = true; 330 | } 331 | if(!$exclude) $f->addOption($iterator->getSubPathName(), $iterator->getSubPathName()); 332 | } 333 | } 334 | } catch (Exception $e) { 335 | //consider putting friendly message here about hitting the windows path limit, but that it won't entirely affect their import. 336 | //$this->message(""); 337 | } 338 | 339 | $form->add($f); 340 | 341 | 342 | $this->addSubmit($form, 'Export'); 343 | 344 | return $form; 345 | } 346 | 347 | /** 348 | * Process the "Step 2" form and populate session variables with the results 349 | * 350 | */ 351 | protected function processExportForm2(InputfieldForm $form) { 352 | 353 | $form->processInput($this->input->post); 354 | 355 | $this->session->treeParent = (int) $form->get('treeParent')->value; 356 | $this->session->export_components = $form->get('export_components')->value; 357 | $this->session->changes_since = $form->get('changes_since')->value; 358 | $this->session->save_or_copy = $form->get('save_or_copy')->value; 359 | $this->session->helper_files = $form->get('helper_files')->value; 360 | 361 | if(count($form->getErrors())){ 362 | 363 | if(!$form->get('treeParent')->value) $this->error("You did not select a parent page. This must be selected to define the content to export."); 364 | if(!$form->get('export_components')->value) $this->error("You did not select the export components."); 365 | if(!$form->get('save_or_copy')->value) $this->error("You did not choose an output format."); 366 | 367 | //$this->session->redirect('./'.$this->session->type); 368 | return $form->render(); 369 | } 370 | 371 | 372 | //find all the relevant pages under the selected parent and then sort them by child level (count path segments) to make sure parents are added to the JSON before their children 373 | if($this->session->changes_since != ''){ 374 | $items = $this->pages->get($this->session->treeParent)->find("modified>{$this->session->changes_since}, id!=2, id!=7, has_parent!=2, has_parent!=7, template!=admin, sort=id, include=all"); // exclude admin and trash in case the user chooses the 'Home' as the parent 375 | } 376 | else{ 377 | $items = $this->pages->get($this->session->treeParent)->find("id!=2, id!=7, has_parent!=2, has_parent!=7, template!=admin, sort=id, include=all"); // exclude admin and trash in case the user chooses the 'Home' as the parent 378 | } 379 | // create empty page array 380 | $res = new PageArray(); 381 | foreach($items as $item) { 382 | // temporarily add pathsegments property to items 383 | $item->pathsegments = count(explode('/',$item->path)); 384 | $res->add($item); 385 | } 386 | //sort based on their level in the page hierarchy which ensures parents get created before their children 387 | $items = $res->filter("sort=pathsegments"); 388 | 389 | 390 | $parent_item = $this->pages->get($this->session->treeParent); 391 | $items->prepend($parent_item); 392 | 393 | if($this->session->save_or_copy == 'copy'){ 394 | return '

Copy this text and import it into your new site using the paste option.

Export more pages

'; 395 | } 396 | else{ 397 | $this->session->jsonFilename = $this->page->filesManager()->path() . 'data.json'; 398 | //header('Content-disposition: attachment; filename='.$this->pages->get($this->session->treeParent)->name.'.json'); 399 | //header('Content-type: application/json'); 400 | //echo ($this->pagesToJSON($items, $this->session->export_components)); 401 | //exit; 402 | 403 | //write json file to assets folder and add it to the zip download 404 | file_put_contents($this->session->jsonFilename, $this->pagesToJSON($items, $this->session->export_components)); 405 | $allfiles = array($this->session->jsonFilename); 406 | $this->create_zip($allfiles, $this->page->filesManager()->path().'files.zip', 'json'); 407 | unlink($this->session->jsonFilename); 408 | 409 | //download the zip to the users 410 | $zipFilename = $this->page->filesManager()->path().'files.zip'; 411 | if (file_exists($zipFilename)) { 412 | header('Content-type: application/zip'); 413 | header('Content-Disposition: attachment; filename='.basename($zipFilename)); 414 | header('Content-length: ' . filesize($zipFilename)); 415 | header('Pragma: no-cache'); 416 | header('Expires: 0'); 417 | readfile($zipFilename); 418 | unlink($zipFilename); 419 | exit; 420 | } 421 | } 422 | 423 | } 424 | 425 | /** 426 | * Build the "Import Step 2" form to import the json file 427 | * 428 | */ 429 | protected function buildImportForm2() { 430 | 431 | $form = $this->modules->get("InputfieldForm"); 432 | $form->method = 'post'; 433 | $form->description = "Step 2: Import"; 434 | 435 | if(class_exists('WireDatabaseBackup')){ //not present in older version of PW - prior to Aug 19, 2014 (approx 2.4.13) 436 | $f = $this->modules->get("InputfieldCheckbox"); 437 | $f->name = 'create_backup'; 438 | $f->label = 'Backup existing database and templates directory'; 439 | $f->description = "Determines whether to backup the existing database and the templates and assets/files directories before importing the new content. Highly Recommended!"; 440 | if($this->session->create_backup) $f->attr('value', $this->session->create_backup); 441 | $f->attr('checked', $this->session->create_backup == '1' ? 'checked' : '' ); 442 | $form->add($f); 443 | } 444 | 445 | $f = $this->modules->get("InputfieldPageListSelect"); 446 | $f->name = 'import_to_parent'; 447 | $f->label = 'Parent Page'; 448 | $f->description = "The parent that you want the imported pages added to.\r\nIMPORTANT NOTE:\r\nThis should be one level up from the parent that you exported, the only exception being if you exported \"Home\", in which case you should still choose \"Home\". \r\nThis is not required if you choose 'Fields and Templates Only' from the options below."; 449 | if($this->session->import_to_parent) $f->attr('value', $this->session->import_to_parent); 450 | $form->add($f); 451 | 452 | $f = $this->modules->get("InputfieldSelect"); 453 | $f->name = 'import_components'; 454 | $f->label = 'Components to Import'; 455 | $f->required = true; 456 | $f->addOption('everything', 'Everything, including all data pages'); 457 | //$f->addOption('fields_templates_and_structural_pages', 'Fields, Templates and Structural Pages'); 458 | $f->addOption('fields_and_templates_only', 'Fields and Templates Only'); 459 | if($this->session->import_components) $f->attr('value', $this->session->import_components); 460 | $form->add($f); 461 | 462 | $f = $this->modules->get("InputfieldSelect"); 463 | $f->name = 'import_type'; 464 | $f->label = 'Import Type'; 465 | $f->required = true; 466 | $f->addOption('append', 'Append'); 467 | $f->addOption('overwrite', 'Overwrite'); 468 | $f->addOption('replace', 'Replace'); 469 | $f->description = "APPEND will not change settings of existing fields, nor the content of existing pages. It will append new fields to templates and new pages (and date) to the selected Parent Page.\nOVERWRITE will change field settings and edit the content of existing pages so they match the imported data.\nREPLACE will match the destination to the source exactly, by modifying page data, changing field type and field settings, and deleting unused fields from templates."; 470 | if($this->session->import_type) $f->attr('value', $this->session->import_type); 471 | $form->add($f); 472 | 473 | $f = $this->modules->get("InputfieldCheckbox"); 474 | $f->name = 'user_details'; 475 | $f->label = 'Import User Details'; 476 | $f->description = "Determines whether to migrate the original createdUser and modifiedUser for each page."; 477 | if($this->session->user_details) $f->attr('value', $this->session->user_details); 478 | $f->attr('checked', $this->session->user_details == '1' ? 'checked' : '' ); 479 | $f->collapsed = Inputfield::collapsedBlank; 480 | $form->add($f); 481 | 482 | $f = $this->modules->get("InputfieldCheckbox"); 483 | $f->name = 'page_dates'; 484 | $f->label = 'Import Created / Modified Dates'; 485 | $f->description = "Determines whether to migrate the original created and modified dates for each page."; 486 | if($this->session->page_dates) $f->attr('value', $this->session->page_dates); 487 | $f->attr('checked', $this->session->page_dates == '1' ? 'checked' : '' ); 488 | $f->collapsed = Inputfield::collapsedBlank; 489 | $form->add($f); 490 | 491 | $f = $this->modules->get("InputfieldCheckbox"); 492 | $f->name = 'download_modules'; 493 | $f->label = 'Automatically Download and Install Missing Fieldtypes'; 494 | $f->description = "Determines whether to automatically download and install missing fieldtypes."; 495 | $f->notes = "If you do not trust the source of the import data, then it is recommended to NOT check this and manually install any missing fieldtypes when warned."; 496 | if($this->session->download_modules) $f->attr('value', $this->session->download_modules); 497 | $f->attr('checked', $this->session->download_modules == '1' ? 'checked' : '' ); 498 | $form->add($f); 499 | 500 | $fieldset = $this->modules->get("InputfieldFieldset"); 501 | $fieldset->attr('id', 'json_source_options'); 502 | $fieldset->label = "Data Source"; 503 | $fieldset->description = "Choose one of the following options as the source of the data.\r\nIf you are importing \"Everything, including all data pages\" and you have files/images in the pages, then you must choose the zip upload.\r\nNB: The structure of this JSON is critical, so it is important that it was created using the export feature of this module."; 504 | $form->add($fieldset); 505 | 506 | $f = $this->modules->get("InputfieldFile"); 507 | $f->name = 'zip_file'; 508 | $f->label = 'Zip File Upload'; 509 | $f->extensions = 'zip'; 510 | $f->maxFiles = 1; 511 | $f->descriptionRows = 0; 512 | $f->overwrite = true; 513 | $f->collapsed = Inputfield::collapsedBlank; 514 | $fieldset->add($f); 515 | 516 | //look for plugin migrator modules and add an importer for each one 517 | $migratorClasses = array(); 518 | foreach($this->wire('modules') as $module) { 519 | $className = $module->className(); 520 | //Look for Migrator in the class name. Might need to make this more specific 521 | if (strpos($className,'Migrator') === false || $className == 'Migrator') continue; 522 | $module = $this->wire('modules')->get($className); 523 | $info = $this->wire('modules')->getModuleInfo($module); 524 | if(!in_array('ProcessMigrator', $info['requires'])) continue; 525 | 526 | $f = $this->modules->get("InputfieldFile"); 527 | $f->name = 'thirdparty_file_'.$className; 528 | $f->label = $info['title']; 529 | $f->extensions = $info['filetype']; 530 | $f->maxFiles = 1; 531 | $f->descriptionRows = 0; 532 | $f->overwrite = true; 533 | $f->collapsed = Inputfield::collapsedBlank; 534 | $fieldset->add($f); 535 | 536 | $migratorClasses[] = $className; 537 | } 538 | 539 | // Little workaround because multidimensional field names aren't allowed 540 | if(isset($migratorClasses)){ 541 | $f = $this->modules->get("InputfieldHidden"); 542 | $f->name = 'migrator_classes'; 543 | $f->value = json_encode($migratorClasses); 544 | $fieldset->add($f); 545 | } 546 | 547 | 548 | $f = $this->modules->get("InputfieldTextarea"); 549 | $f->name = 'json_data'; 550 | $f->label = 'Paste in JSON Data'; 551 | $f->collapsed = Inputfield::collapsedBlank; 552 | $fieldset->add($f); 553 | 554 | $f = $this->modules->get("InputfieldSelect"); 555 | $f->name = 'json_package'; 556 | $f->label = 'Shared JSON packages'; 557 | //$packages = json_decode(file_get_contents('https://raw.github.com/adrianbj/ProcessWirePageLists/master/packages.json')); 558 | $options = array('http' => array('user_agent' => 'adrianbj')); 559 | $context = stream_context_create($options); 560 | $packages = json_decode(file_get_contents('https://api.github.com/repos/adrianbj/ProcessWirePageLists/contents/', false, $context)); 561 | if(!is_array($packages)) { 562 | $this->error("Github rate limit has been exceeded. Please try again shortly."); 563 | $f->description = __("Github rate limit has been exceeded. Please try again shortly."); 564 | } 565 | else{ 566 | $f->addOption(''); 567 | foreach($packages as $package){ 568 | if(pathinfo($package->html_url, PATHINFO_EXTENSION) != "json") continue; //exclude readme, license etc. Only looking for JSON files 569 | $package_name = pathinfo($package->html_url, PATHINFO_FILENAME); 570 | $package_raw_url = str_replace('//','//raw.', str_replace('blob/','',$package->html_url)); 571 | $f->addOption($package_raw_url, $package_name); 572 | } 573 | if($this->session->json_package) $f->attr('value', $this->session->json_package); 574 | $f->description = __("Select from one of the shared JSON packages.\r\nMore details about these packages are available at the ProcessWirePageLists Github page: [https://github.com/adrianbj/ProcessWirePageLists](https://github.com/adrianbj/ProcessWirePageLists)"); 575 | } 576 | $f->collapsed = Inputfield::collapsedBlank; 577 | $fieldset->add($f); 578 | 579 | $f = $this->modules->get("InputfieldURL"); 580 | $f->name = 'json_url'; 581 | $f->label = 'URL to JSON file'; 582 | $f->description = "Enter a URL directly to a .json file, eg: [https://raw.github.com/adrianbj/ProcessWirePageLists/master/countries.json](https://raw.github.com/adrianbj/ProcessWirePageLists/master/countries.json)"; 583 | $f->collapsed = Inputfield::collapsedBlank; 584 | $fieldset->add($f); 585 | 586 | 587 | $f = $this->modules->get("InputfieldCheckbox"); 588 | $f->name = 'edit_imported_content'; 589 | $f->label = 'Edit Imported Content'; 590 | $f->description = "If checked you will get another step where you can choose exactly which pages and fields you want to import."; 591 | $f->attr('checked', $this->session->edit_imported_content == '1' ? 'checked' : '' ); 592 | //$f->collapsed = Inputfield::collapsedBlank; 593 | $f->collapsed = $f->attr('checked') ? Inputfield::collapsedNo : Inputfield::collapsedYes; 594 | $form->add($f); 595 | 596 | 597 | $this->addSubmit($form, 'Upload and Create Content'); 598 | 599 | return $form; 600 | } 601 | 602 | 603 | 604 | /** 605 | * Build the "Restore Step 2" form to restore database backup 606 | * 607 | */ 608 | protected function buildRestoreForm2() { 609 | 610 | $form = $this->modules->get("InputfieldForm"); 611 | $form->method = 'post'; 612 | 613 | $f = $this->modules->get("InputfieldSelect"); 614 | $f->name = 'restore_directory'; 615 | $f->label = 'Backup to Restore'; 616 | $f->required = true; 617 | 618 | if(file_exists($this->config->paths->assets.'migratorbackups/') && !$this->is_dir_empty($this->config->paths->assets.'migratorbackups/')){ 619 | 620 | $form->description = "Step 2: Restore"; 621 | 622 | foreach($iterator = new RecursiveDirectoryIterator($this->config->paths->assets.'migratorbackups/', RecursiveDirectoryIterator::SKIP_DOTS) as $item){ 623 | if ($item->isDir()) { 624 | if(strpos($iterator->getSubPathName(),'_') !== false){ 625 | //convert dir name to friendly date / time format for restore select dropdown 626 | $optionLabel = strstr($iterator->getSubPathName(), '_', true) . " " . str_replace("-", ":", str_replace("_", "", strstr($iterator->getSubPathName(), '_'))); 627 | } 628 | else{ 629 | $optionLabel = $iterator->getSubPathName(); // just for anyone who installed the module before the date format changed 630 | } 631 | $f->addOption($iterator->getSubPathName(), $optionLabel); 632 | } 633 | } 634 | if($this->session->restore_directory) $f->attr('value', $this->session->restore_directory); 635 | $form->add($f); 636 | 637 | $this->addSubmit($form, 'Restore'); 638 | } 639 | else{ 640 | $form->description = "Sorry, there are no backups to restore."; 641 | } 642 | 643 | return $form; 644 | 645 | } 646 | 647 | 648 | 649 | /** 650 | * Build the "Import Step 3" form to determine what pages/fields get imported 651 | * 652 | */ 653 | protected function buildImportForm3($data) { 654 | 655 | $form = $this->modules->get("InputfieldForm"); 656 | $form->method = 'post'; 657 | $form->description = "Step 3: Edit Content to be Imported"; 658 | 659 | 660 | $f = $this->modules->get("InputfieldAsmSelect"); 661 | $f->name = 'import_fields'; 662 | $f->label = 'Excluded Fields'; 663 | $f->required = true; 664 | 665 | foreach($data->fields as $np){ 666 | $f->addOption($np->name, $np->name); 667 | //$f->attr('value', $np->name); 668 | } 669 | 670 | $f->description = "By default, all fields are imported. Select any fields that you DON'T want to import."; 671 | $f->setAsmSelectOption('sortable', false); 672 | $form->add($f); 673 | 674 | if(isset($data->pages) && $this->session->import_components != 'fields_and_templates_only'){ 675 | $f = $this->modules->get("InputfieldAsmSelect"); 676 | $f->name = 'import_pages'; 677 | $f->label = 'Excluded Pages'; 678 | $f->required = true; 679 | 680 | foreach($data->pages as $np){ 681 | $f->addOption($np->name, $np->name); 682 | //$f->attr('value', $np->name); 683 | } 684 | 685 | $f->description = "By default, all pages are imported. Select any pages that you DON'T want to import."; 686 | $f->setAsmSelectOption('sortable', false); 687 | $form->add($f); 688 | } 689 | 690 | //these hidden fields are a bit of a hack to prevent field required notices when processing this form because we are using the same code to process Input Form2 and Form3 and session variables are being lost somewhere 691 | $f = $this->modules->get("InputfieldHidden"); 692 | $f->name = 'create_backup'; 693 | if($this->session->create_backup) $f->attr('value', $this->session->create_backup); 694 | $form->add($f); 695 | 696 | $f = $this->modules->get("InputfieldHidden"); 697 | $f->name = 'import_to_parent'; 698 | if($this->session->import_to_parent) $f->attr('value', $this->session->import_to_parent); 699 | $form->add($f); 700 | 701 | $f = $this->modules->get("InputfieldHidden"); 702 | $f->name = 'import_type'; 703 | if($this->session->import_type) $f->attr('value', $this->session->import_type); 704 | $form->add($f); 705 | 706 | $f = $this->modules->get("InputfieldHidden"); 707 | $f->name = 'import_components'; 708 | if($this->session->import_components) $f->attr('value', $this->session->import_components); 709 | $form->add($f); 710 | 711 | $f = $this->modules->get("InputfieldHidden"); 712 | $f->name = 'download_modules'; 713 | if($this->session->download_modules) $f->attr('value', $this->session->download_modules); 714 | $form->add($f); 715 | 716 | $f = $this->modules->get("InputfieldHidden"); 717 | $f->name = 'jsonFilename'; 718 | if($this->session->jsonFilename) $f->attr('value', $this->session->jsonFilename); 719 | $form->add($f); 720 | 721 | $this->addSubmit($form, 'Create Content'); 722 | 723 | return $form; 724 | } 725 | 726 | 727 | /** 728 | * Process the "Import Step 2" form and upload the zip/json file 729 | * 730 | */ 731 | protected function processImportForm2(InputfieldForm $form) { 732 | 733 | $this->recursiveDelete($this->page->filesManager()->path(), false); //cleanup anything left in the Migrator assets/files directory from previous failed import 734 | 735 | $form->processInput($this->input->post); 736 | //$errors = $form->getErrors(true); used to delete automatic field errors since we want to provide custom ones 737 | if(count($form->getErrors())) return false; 738 | 739 | if($this->input->post){ 740 | //because these two are coming from InputForm3?, they have to use $this->input->post and not $form->get()->value 741 | $this->session->import_fields = isset($this->input->post->import_fields) ? $this->input->post->import_fields : ''; 742 | $this->session->import_pages = isset($this->input->post->import_pages) ? $this->input->post->import_pages : ''; 743 | /* 744 | $this->session->create_backup = $form->get('create_backup')->value; 745 | $this->session->import_to_parent = (int) $form->get('import_to_parent')->value; 746 | $this->session->import_to_parent = (int) $this->input->post->import_to_parent; 747 | $this->session->import_components = $form->get('import_components')->value; 748 | $this->session->import_components = $this->input->post->import_components; 749 | $this->session->user_details = $form->get('user_details')->value; 750 | $this->session->page_dates = $form->get('page_dates')->value; 751 | $this->session->download_modules = $form->get('download_modules')->value; 752 | $this->session->import_type = $form->get('import_type')->value; 753 | if(isset($form->get('jsonFilename')->value)) $this->session->jsonFilename = $form->get('jsonFilename')->value; 754 | $this->session->edit_imported_content = isset($form->get('import_fields')->value) ? $this->session->edit_imported_content : $form->get('edit_imported_content')->value; 755 | */ 756 | $this->session->create_backup = $this->input->post->create_backup; 757 | $this->session->import_to_parent = (int) $this->input->post->import_to_parent; 758 | $this->session->import_to_parent = (int) $this->input->post->import_to_parent; 759 | $this->session->import_components = $this->input->post->import_components; 760 | $this->session->import_components = $this->input->post->import_components; 761 | $this->session->user_details = $this->input->post->user_details; 762 | $this->session->page_dates = $this->input->post->page_dates; 763 | $this->session->download_modules = $this->input->post->download_modules; 764 | $this->session->import_type = $this->input->post->import_type; 765 | if(isset($this->input->post->jsonFilename)) $this->session->jsonFilename = $this->input->post->jsonFilename; 766 | $this->session->edit_imported_content = isset($this->input->post->import_fields) ? $this->session->edit_imported_content : $this->input->post->edit_imported_content; 767 | 768 | 769 | } 770 | 771 | if($this->session->create_backup == 1){ 772 | $backupDir = $this->config->paths->assets.'migratorbackups/'.date('Y-m-d_H-i-s'); 773 | 774 | if (!file_exists($this->config->paths->assets.'migratorbackups/')) mkdir($this->config->paths->assets.'migratorbackups/'); 775 | if (!file_exists($backupDir)) mkdir($backupDir); 776 | 777 | $backup = new WireDatabaseBackup($backupDir.'/'); 778 | $backup->setDatabase($this->database); 779 | $backup->setDatabaseConfig($this->config); 780 | 781 | $file = $backup->backup(array('filename' => 'migratorbackup.sql')); 782 | //copy templates and files directory to backup location 783 | wireCopy($this->config->paths->templates, $backupDir . '/templates/', true); 784 | wireCopy($this->config->paths->files, $backupDir . '/files/', true); 785 | 786 | // remove uploaded file from the backup directory by emptying the files page folder connected with Migrator 787 | // don't want this restored or we get an error when importing after restore because file already exists 788 | $migratorClassFilesDir = str_replace($this->config->paths->assets, '', $this->page->filesManager()->path()); 789 | $this->recursiveDelete($backupDir . '/' . $migratorClassFilesDir, false); 790 | } 791 | 792 | if(!$this->session->import_to_parent && $this->session->import_components != 'fields_and_templates_only') { 793 | if($this->session->migratorFilesDir && file_exists($this->session->migratorFilesDir)) $this->recursiveDelete($this->session->migratorFilesDir); 794 | $this->error("Missing required parent page. This must be selected if you want to import the pages in addition to field and template creation."); 795 | //$this->session->redirect('./'.$this->session->type); 796 | return $form->render(); 797 | } 798 | 799 | //for submission of form either without Edit Imported Content, or the first submission to get the fields/pages from the JSON file 800 | if(!isset($form->get('import_fields')->value) || $form->get('import_fields')->value==''){ 801 | 802 | /*$this->session->zipFile = $form->get('zip_file')->value; 803 | if($form->get('json_data')->value != '') $this->session->jsonData = $form->get('json_data')->value; 804 | 805 | $this->session->jsonURL = $form->get('json_url')->value;*/ 806 | 807 | /*$zipFile = $form->get('zip_file')->value != '' ? $form->get('zip_file')->value : ''; 808 | 809 | $this->session->jsonPackage = $form->get('json_package')->value != '' ? $form->get('json_package')->value : $this->session->jsonPackage; 810 | $this->session->jsonData = $form->get('json_data')->value != '' ? $form->get('json_data')->value : $this->session->jsonData; 811 | $this->session->jsonURL = $form->get('json_url')->value != '' ? $form->get('json_url')->value : $this->session->jsonURL;*/ 812 | 813 | if($form->get('json_package')->value != ''){ 814 | $this->session->jsonPackage = $form->get('json_package')->value; 815 | $this->session->remove('jsonData'); 816 | $this->session->remove('jsonURL'); 817 | $this->session->remove('zipFilename'); 818 | } 819 | 820 | if($form->get('json_data')->value != ''){ 821 | $this->session->remove('jsonPackage'); 822 | $this->session->jsonData = $form->get('json_data')->value; 823 | $this->session->remove('jsonURL'); 824 | $this->session->remove('zipFilename'); 825 | } 826 | 827 | if($form->get('json_url')->value != ''){ 828 | $this->session->remove('jsonPackage'); 829 | $this->session->remove('jsonData'); 830 | $this->session->jsonURL = $form->get('json_url')->value; 831 | $this->session->remove('zipFilename'); 832 | } 833 | 834 | if($form->get('zip_file')->value != ''){ 835 | $this->session->remove('jsonPackage'); 836 | $this->session->remove('jsonData'); 837 | $this->session->remove('jsonURL'); 838 | $zipFile = $form->get('zip_file')->value; 839 | //if(is_array($zipFile) && count($zipFile)>0){ 840 | $this->session->zipFile = $zipFile->first(); 841 | $this->session->zipFile->rename("data.zip"); 842 | $this->session->zipFilename = $this->session->zipFile->filename; 843 | //} 844 | } 845 | 846 | $migratorClasses = json_decode($form->get('migrator_classes')->value); 847 | if(is_array($migratorClasses)){ 848 | foreach($migratorClasses as $migratorClass) { 849 | if($form->get('thirdparty_file_'.$migratorClass)->value == '') continue; 850 | $this->session->remove('jsonPackage'); 851 | $this->session->remove('jsonData'); 852 | $this->session->remove('jsonURL'); 853 | $this->session->remove('thirdpartyFilename'); 854 | $thirdpartyFile = $form->get('thirdparty_file_'.$migratorClass)->value; 855 | rename($this->page->filesManager()->path() . $thirdpartyFile, $this->page->filesManager()->path() . 'thirdpartydata.txt'); 856 | $this->session->thirdpartyFilename = $this->page->filesManager()->path() . 'thirdpartydata.txt'; 857 | $this->session->thirdpartyModule = $migratorClass; 858 | } 859 | } 860 | 861 | 862 | /*if(is_array($zipFile) && count($zipFile)>0) { 863 | $this->session->zipFile = $zipFile->first(); 864 | $this->session->zipFile->rename("data.zip"); 865 | $this->session->zipFilename = $this->session->zipFile->filename;*/ 866 | $this->session->migratorFilesDir = $this->page->filesManager()->path() . 'migratorfiles'; 867 | $this->session->jsonFilename = $this->session->migratorFilesDir . '/data.json'; 868 | 869 | if($this->session->zipFilename){ 870 | // extract uploaded zip to destination PW installation 871 | $zip = new ZipArchive; 872 | if($zip->open($this->session->zipFilename) === TRUE) { 873 | $zip->extractTo($this->session->migratorFilesDir); 874 | $zip->close(); 875 | unlink($this->session->zipFilename); 876 | 877 | // set paths for moving files into the destination PW site's templates folder 878 | $srcDir = $this->session->migratorFilesDir . '/templates/'; 879 | $destDir = $this->config->paths->templates.'/'; 880 | 881 | // check write permissions on templates directory and fail with friendly error 882 | if(file_exists($srcDir) && !is_writable($destDir)){ 883 | if($this->session->migratorFilesDir && file_exists($this->session->migratorFilesDir)) $this->recursiveDelete($this->session->migratorFilesDir); 884 | $this->error("There are template PHP files in your import, but the templates directory is not writeable. Please change permissions and try again."); 885 | //$this->session->redirect('./'.$this->session->type); 886 | $form->get('zip_file')->value = ''; 887 | return $form->render(); 888 | } 889 | 890 | //move template and other helper files into the templates directory 891 | if (file_exists($srcDir) && is_dir($srcDir) && $handle = opendir($srcDir)) { 892 | foreach($iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($srcDir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST) as $item){ 893 | $this->migratedTemplateFileNames[] = str_replace($this->session->migratorFilesDir, '', $item); 894 | if ($item->isDir()) { 895 | if(!file_exists($destDir . $iterator->getSubPathName())){ 896 | mkdir($destDir . str_replace("//", "/", $iterator->getSubPathName())); 897 | } 898 | } 899 | else { 900 | copy($item, $destDir . str_replace("//", "/", $iterator->getSubPathName())); 901 | } 902 | } 903 | } 904 | } 905 | } 906 | elseif(file_exists($this->session->migratorFilesDir)){ 907 | //no need to do anything since the directory of files already exists 908 | //this would be the case when "Edit Imported Content" was selected. 909 | } 910 | else{ 911 | if($this->session->jsonData) { 912 | $json = $this->session->jsonData; 913 | } 914 | elseif($this->session->jsonPackage) { 915 | $json = file_get_contents($this->session->jsonPackage); 916 | } 917 | elseif($this->session->jsonURL) { 918 | $json = file_get_contents($this->session->jsonURL); 919 | } 920 | elseif($this->session->thirdpartyFilename){ 921 | // loading the third party data 922 | if (!file_exists($this->session->migratorFilesDir)) mkdir($this->session->migratorFilesDir); 923 | 924 | // Load Thirdparty Migrator 925 | $migrator = $this->modules->get($this->session->thirdpartyModule); 926 | 927 | //convertToJson function must be defined in the 3rd party module 928 | $json = $migrator->convertToJson($this->page->filesManager()->path() . '/thirdpartydata.txt'); 929 | $this->session->jsonData = $json; 930 | 931 | //just for testing/debugging json 932 | //file_put_contents($this->page->filesManager()->path() . '/thirdpartydata.json', $json); 933 | 934 | // remove original third party data file 935 | unlink($this->page->filesManager()->path() . '/thirdpartydata.txt'); 936 | } 937 | else{ 938 | $this->error("Missing required ZIP or JSON Source"); 939 | if($this->session->migratorFilesDir && file_exists($this->session->migratorFilesDir)) $this->recursiveDelete($this->session->migratorFilesDir); 940 | return $form->render(); 941 | } 942 | 943 | if (!file_exists($this->session->migratorFilesDir)) mkdir($this->session->migratorFilesDir); 944 | file_put_contents($this->session->jsonFilename, $json); 945 | //exit; 946 | 947 | } 948 | 949 | //populate $fp with data from json file written to the server from pasted, or externally linked JSON file 950 | $fp = file_get_contents($this->session->jsonFilename); 951 | 952 | 953 | //if no data source provided, return an error. This check probably isn't necessary because of the ones above. 954 | if(empty($fp)){ 955 | $this->error("Missing required ZIP or JSON Source"); 956 | if($this->session->migratorFilesDir && file_exists($this->session->migratorFilesDir)) $this->recursiveDelete($this->session->migratorFilesDir); 957 | //$this->session->redirect('./'.$this->session->type); 958 | return $form->render(); 959 | } 960 | 961 | //populate $data with json string of all the content to be created 962 | $data = json_decode($fp); 963 | 964 | } 965 | 966 | 967 | 968 | //if selected, redirect to form to allow user to determine which pages/fields get imported 969 | if($this->input->post->edit_imported_content=='1'){ 970 | $form = $this->buildImportForm3($data); 971 | return $form->render(); 972 | } 973 | 974 | //now that we have been through both ImportForm 2 and 3, it's ok to delete the json.data file from the migratorfiles temp directory. 975 | //unlink($this->session->jsonFilename); 976 | 977 | 978 | //check fieldtypes of the fields to be installed against the available ones in the destination install before attempting to save a field with a type that isn't available. 979 | //attempt to install it if it is available (core and those site modules that are downloaded but not installed) 980 | //TODO: should maybe switch to $this->modules->getInstall() - https://processwire.com/talk/topic/6449-install-module-from-api-when-module-is-not-listed/?p=63127 981 | //Maybe also use "isInstalled" to check first, although everything does seem to be working as is: https://processwire.com/talk/topic/6450-how-do-we-check-if-a-module-is-activated/?p=63126 982 | $missing_fieldtypes = array(); 983 | 984 | foreach($data->fields as $np){ 985 | 986 | //automatically install language support if needed 987 | if(strpos($np->type, 'Language') !== false){ 988 | $this->modules->get("LanguageSupport"); 989 | if(strpos($np->type, 'Fieldtype') !== false) $this->modules->get("LanguageSupportFields"); 990 | 991 | if(count($this->languages) < 2) $missing_fieldtypes[] = 'Missing additional language pack(s)'; 992 | } 993 | 994 | if(!in_array($np->type, $this->fieldtypes->getArray()) && !$this->modules->get($np->type)){ 995 | if(!$this->downloadConfirm($np->type)) { // attempt to download and install. If not possible, then add to missing list error 996 | $missing_fieldtypes[] = $np->type; 997 | } 998 | } 999 | 1000 | } 1001 | 1002 | foreach($data->pages as $np){ 1003 | if(isset($np->name_default_name)) $this->modules->get("LanguageSupportPageNames"); 1004 | } 1005 | 1006 | //input field classes - eg. CKEditor 1007 | foreach($data->fields as $np){ 1008 | if(isset($np->data->inputfieldClass) && !$this->modules->isInstalled($np->data->inputfieldClass) && !$this->modules->getInstall($np->data->inputfieldClass)){ 1009 | if(!$this->downloadConfirm($np->data->inputfieldClass)) { // attempt to download and install. If not possible, then add to missing list error 1010 | $missing_fieldtypes[] = $np->data->inputfieldClass; 1011 | } 1012 | } 1013 | } 1014 | 1015 | //input fields - eg. InputfieldSelectMultipleTransfer 1016 | foreach($data->fields as $np){ 1017 | if(isset($np->data->inputfield)) $npInputfield = str_replace('_','',$np->data->inputfield); 1018 | if(isset($np->data->inputfield) && !$this->modules->isInstalled($npInputfield) && !$this->modules->getInstall($npInputfield)){ 1019 | if(!$this->downloadConfirm($npInputfield)) { // attempt to download and install. If not possible, then add to missing list error 1020 | $missing_fieldtypes[] = $npInputfield; 1021 | } 1022 | } 1023 | } 1024 | 1025 | if(count($missing_fieldtypes)>0){ 1026 | if($this->session->migratorFilesDir && file_exists($this->session->migratorFilesDir)) $this->recursiveDelete($this->session->migratorFilesDir); 1027 | $this->error($this->fancy_implode(array_unique($missing_fieldtypes)). " required by the imported content, but not available in this Processwire setup. Please install and then import again."); 1028 | if(isset($zipFile)) $form->get('zip_file')->value = ''; 1029 | return $form->render(); 1030 | } 1031 | 1032 | 1033 | //cleanup to make repeaters work when using REPLACE action - PW doesn't seem to do this properly itself. 1034 | if($this->session->import_type == "replace"){ 1035 | foreach($data->fields as $np){ 1036 | if(isset($np->data)) { 1037 | foreach($np->data as $np_field_name => $np_field_value){ 1038 | if($this->modules->get($np->type) == "FieldtypeRepeater"){ 1039 | if($this->fields->get("{$np->name}")) $current_field_id = $this->fields->get("{$np->name}")->id; 1040 | if(isset($current_field_id)){ 1041 | $forfieldid = "for-field-$current_field_id"; 1042 | $sql = "DELETE FROM pages WHERE name=:forfieldid"; 1043 | $query = $this->wire('database')->prepare($sql); 1044 | $query->bindValue(':forfieldid', $forfieldid); 1045 | $query->execute(); 1046 | } 1047 | } 1048 | } 1049 | } 1050 | } 1051 | } 1052 | 1053 | //Settings - so far only for 3rd party migrators 1054 | if(isset($data->settings)){ 1055 | foreach($data->settings as $setting){ 1056 | $this->base_url = $setting->base_url; 1057 | $this->thumb_suffix = $setting->thumb_suffix; 1058 | } 1059 | } 1060 | 1061 | 1062 | //Templates - first iteration to create templates 1063 | foreach($data->templates as $np){ 1064 | 1065 | if(!$this->fieldgroups->{$np->template}) { 1066 | $fg = new Fieldgroup(); 1067 | $fg->name = $np->template; 1068 | $fg->add("title"); 1069 | $fg->save(); 1070 | } 1071 | else{ 1072 | $fg = $this->fieldgroups->{$np->template}; 1073 | } 1074 | 1075 | if(!$this->templates->{$np->template}) { 1076 | $template = new Template(); 1077 | $template->name = $np->template; 1078 | $template->fieldgroup = $fg; 1079 | $template->save(); 1080 | } 1081 | else{ 1082 | $template = $this->templates->{$np->template}; 1083 | } 1084 | 1085 | } 1086 | 1087 | 1088 | //Templates - second iteration to save settings. 1089 | // had to separate because it isn't possible to populate childTemplates / parentTemplates arrays if the template to be set isn't created yet 1090 | foreach($data->templates as $np){ 1091 | 1092 | $template = $this->templates->{$np->template}; 1093 | $fg = $this->fieldgroups->{$np->template}; 1094 | 1095 | if(isset($np->data)) { 1096 | foreach($np->data as $np_field_name => $np_field_value){ 1097 | //Roles (Access Tab) 1098 | if(stripos($np_field_name,'Roles') !== false && is_array($np_field_value)) { 1099 | $id = array_map(array($this, 'getRoleIDFromName'), $np_field_value); 1100 | if($id) $template->$np_field_name = $id; 1101 | } 1102 | //Child and Parent template settings (Family Tab) 1103 | elseif(is_array($np_field_value)) { 1104 | $id = array_map(array($this, 'getTemplateIDFromName'), $np_field_value); 1105 | $template->$np_field_name = $id; 1106 | } 1107 | else{ 1108 | $template->$np_field_name = $np_field_value; 1109 | } 1110 | } 1111 | } 1112 | 1113 | $template->fieldgroup = $fg; 1114 | $template->save(); 1115 | 1116 | } 1117 | 1118 | 1119 | //Fields 1120 | $pagefield_parent_ids = array(); 1121 | $pagetablefield_parent_ids = array(); 1122 | $fieldsInTemplates = array(); 1123 | $repeaterFieldsInTemplates = array(); 1124 | $deletedFields = array(); 1125 | foreach($data->fields as $np){ 1126 | 1127 | //if field is not in the list of selected import fields, then skip 1128 | if(is_array($this->session->import_fields) && in_array($np->name, $this->session->import_fields)) continue; 1129 | 1130 | //if replace mode then we need to delete the field first in case there is an incompatible field type change 1131 | if(!in_array($np->name, $deletedFields) && $this->fields->get($np->name) && $this->fields->get($np->name)->flags <= 1 && ($this->session->import_type == "replace")) { 1132 | //first remove the field from all templates before deleting it. 1133 | foreach($this->templates as $t){ 1134 | foreach($t->fieldgroup as $field){ 1135 | if($field->name == $np->name){ 1136 | $t->fieldgroup->remove($field); 1137 | $t->fieldgroup->save(); 1138 | } 1139 | } 1140 | } 1141 | try{ 1142 | //delete the field 1143 | $f = $this->fields->get($np->name); 1144 | $this->fields->delete($f); 1145 | $deletedFields[] = $np->name; // populate array to check against so that we don't delete again a field that was already deleted 1146 | } 1147 | catch(Exception $e) { 1148 | // likely a repeater that can't be deleted because it is used by pages 1149 | } 1150 | } 1151 | 1152 | //if field doesn't exist, then create it now 1153 | if(!$this->fields->get($np->name)){ 1154 | $field = new Field(); 1155 | $field->type = $this->modules->get($np->type); 1156 | $field->name = $np->name; 1157 | try{ 1158 | $field->save(); 1159 | } 1160 | catch(Exception $e) { 1161 | // likely the DB table already exists because a restore didn't remove the tables 1162 | } 1163 | $this->newField = true; 1164 | } 1165 | else{ 1166 | $field = $this->fields->get($np->name); 1167 | } 1168 | 1169 | //this is necessary to make sure additional fields are added to DB for certain field types. 1170 | //was getting "Unknown column 'field_images.modified'" errors if importing to new PW install before any pages with image fields had been edited and so the modified and created fields weren't being added to the database. 1171 | $ft = $this->modules->get($np->type); 1172 | $ft->getDatabaseSchema($field); 1173 | 1174 | 1175 | $template = $this->templates->{$np->template}; 1176 | 1177 | if($template) { //standard fields in template, not repeater fields 1178 | $fieldsInTemplates[$template->name][] = $field->name; //populate for later checking of fields to be deleted 1179 | } 1180 | 1181 | if($this->newField || $this->session->import_type == "replace"){ 1182 | 1183 | $field->label = $np->label; 1184 | $field->description = $np->description; 1185 | $field->flags = $np->flags; 1186 | $field->save(); 1187 | 1188 | try{ 1189 | $field->type = $np->type; 1190 | $field->save(); 1191 | } 1192 | catch(Exception $e) { 1193 | $this->error("Could not change the field type of {$np->name} because it is not a deletable field and the new and old type are incompatible. This may cause problems with the imported content."); 1194 | } 1195 | 1196 | if(isset($np->data)) { 1197 | foreach($np->data as $np_field_name => $np_field_value){ 1198 | 1199 | //manually add tags field to DB table because it's not happening automatically, even though it should be using getDatabaseSchema above: 1200 | //https://github.com/ryancramerdesign/ProcessWire/blob/03387f8283d518e9cc405eff8f05cd6a5bf77c4c/wire/modules/Fieldtype/FieldtypeFile.module#L311 1201 | if($np_field_name == 'useTags' && $np_field_value==1){ 1202 | try{ 1203 | $sql = "ALTER TABLE `field_".$field->name."` ADD `tags` TEXT NOT NULL"; 1204 | $query = $this->wire('database')->prepare($sql); 1205 | $query->execute(); 1206 | } 1207 | catch(Exception $e) { 1208 | // intentionally blank - in case tags field already exists there would be an error 1209 | } 1210 | } 1211 | 1212 | 1213 | if(strpos($this->modules->get($np->type), 'Language') !== false) { 1214 | if(strpos($np_field_name, 'label_') !== false){ 1215 | $language = str_replace('_','',strstr($np_field_name, '_')); 1216 | $language_id = $this->languages->get($language)->id; 1217 | $np_field_name = 'label'.$language_id; 1218 | } 1219 | } 1220 | 1221 | if($this->modules->get($np->type) == "FieldtypeRepeater"){ 1222 | 1223 | $this->modules->get("FieldtypeRepeater"); // install repeater module if it's not already installed. Probably not needed here as we do this above already. 1224 | 1225 | $repeater_fieldgroup = "repeater_{$np->name}"; 1226 | 1227 | if(!$this->fieldgroups->$repeater_fieldgroup) { 1228 | $repeater_fg = new Fieldgroup(); 1229 | $repeater_fg->name = $repeater_fieldgroup; 1230 | } 1231 | else{ 1232 | $repeater_fg = $this->fieldgroups->$repeater_fieldgroup; 1233 | } 1234 | 1235 | if(is_array($np_field_value)) { 1236 | foreach($np_field_value as $rf){ 1237 | $repeater_fg->append($rf); // populates fieldgroups_fields with IDs of repeater subfields 1238 | $repeaterFieldsInTemplates[$np->name][] = $rf; //populate repeater subfields for later checking of fields to be deleted 1239 | } 1240 | } 1241 | $repeater_fg->save(); 1242 | 1243 | if(!$this->templates->$repeater_fieldgroup) { 1244 | $repeater_template = new Template(); 1245 | $repeater_template->name = $repeater_fieldgroup; 1246 | $repeater_template->flags = 8; 1247 | $repeater_template->noChildren = 1; 1248 | $repeater_template->noParents = 1; 1249 | $repeater_template->noGlobal = 1; 1250 | $repeater_template->slashUrls = 1; 1251 | $repeater_template->fieldgroup = $repeater_fg; 1252 | $repeater_template->save(); 1253 | } 1254 | else{ 1255 | $repeater_template = $this->templates->$repeater_fieldgroup; 1256 | } 1257 | 1258 | // need to override these values in the JSON data because they come from the source PW install and aren't relevant here when importing 1259 | if($np_field_name == "template_id") $np_field_value = $repeater_template->id; 1260 | if($np_field_name == "parent_id") { 1261 | $repeater_page = "for-field-{$field->id}"; 1262 | $np_field_value = $this->pages->get("name={$repeater_page}, include=all, has_parent!=7")->id; 1263 | } 1264 | } 1265 | 1266 | if($this->modules->get($np->type) == "FieldtypePage"){ 1267 | // need to override these values in the JSON data because they come from the source PW install and aren't relevant here when importing 1268 | if($np_field_name == "template_id") $np_field_value = $this->templates->get("name=$np_field_value")->id; 1269 | // populate array with parent_id names for page fields so that they can be added once the parent page has been created below in the pages section 1270 | if($np_field_name == "parent_id") $pagefield_parent_ids[$np->name] = $np_field_value; 1271 | } 1272 | 1273 | if($this->modules->get($np->type) == "FieldtypePageTable"){ 1274 | // get the new page id for the parent of the PageTable items 1275 | if($np_field_name == "parent_id"){ 1276 | $np_field_value = $this->pages->get("name=$np_field_value")->id; 1277 | $pagetablefield_parent_ids[$np->name] = $np_field_value; 1278 | } 1279 | } 1280 | 1281 | //not sure about the new addition (2014-03-15) checking for instance of FieldtypeFile ?? 1282 | //was an attempt to fix broken image field creation, but I don't think it is related, although might still be a good check to have 1283 | if(is_array($np_field_value) && !$this->modules->get($np->type) instanceof FieldtypeFile) { // think this is limited to repeaters and nothing else - need to check 1284 | $id = array_map(array($this, 'getFieldIDFromName'), $np_field_value); 1285 | $field->$np_field_name = $id; // populates fields > data > repeaterFields with IDs of repeater subfields 1286 | } 1287 | else{ 1288 | $field->$np_field_name = $np_field_value; 1289 | } 1290 | 1291 | } 1292 | } 1293 | 1294 | //update db table with required Table fields 1295 | if($field->type=="FieldtypeTable") $field->type->_checkSchema($field, true); 1296 | 1297 | try{ 1298 | $field->save(); 1299 | } 1300 | catch(Exception $e) { 1301 | // more catching of unsuccessful attempts to change field type 1302 | } 1303 | 1304 | if($template) $template->fieldgroup->append($field); // add new field to template. If $template checks are for repeater subfields - don't want to add these to a standard template 1305 | } 1306 | else{ 1307 | if($template) $template->fieldgroup->append($this->fields->{$np->name}); // add existing field to template - NB: this does not change any of the attributes of a field if it already exists - this could be problematic for the import 1308 | } 1309 | 1310 | if($template) $template->fieldgroup->save(); 1311 | 1312 | //$field->save(); 1313 | 1314 | } 1315 | 1316 | 1317 | 1318 | //Remove fields no longer in templates when using the REPLACE import action 1319 | if($this->session->import_type == "replace"){ 1320 | 1321 | $templateArray = array(); 1322 | $repeaterFieldsArray = array(); 1323 | foreach($fieldsInTemplates as $check_temp => $fields){ 1324 | $templateArray[] = $check_temp; 1325 | } 1326 | 1327 | //Normal fields 1328 | foreach($templateArray as $template){ 1329 | if($template=='') continue; //blank template means a repeater field due to the way the JSON is contructed 1330 | foreach($this->templates->get("$template")->fields as $des_field){ 1331 | if(!in_array($des_field, $fieldsInTemplates[$template]) && $des_field != 'title'){ 1332 | $this->templates->get("$template")->fieldgroup->remove($this->fields->get("$des_field")); 1333 | $this->templates->get("$template")->fieldgroup->save(); 1334 | } 1335 | } 1336 | } 1337 | 1338 | //Repeater fields 1339 | foreach($fieldsInTemplates as $check_temp => $fields){ 1340 | foreach($fields as $field){ 1341 | if($this->fields->get("$field")->type == "FieldtypeRepeater") $repeaterFieldsArray[] = $field; 1342 | } 1343 | } 1344 | 1345 | foreach($repeaterFieldsArray as $repeaterField){ 1346 | $repTemplate = $this->templates->get("repeater_$repeaterField"); 1347 | foreach($repTemplate->fieldgroup as $rf){ 1348 | if(!in_array($rf->name, $repeaterFieldsInTemplates[$repeaterField])){ 1349 | $repTemplate->fieldgroup->remove($this->fields->get("$rf->name")); 1350 | $repTemplate->fieldgroup->save(); 1351 | } 1352 | } 1353 | } 1354 | } 1355 | 1356 | 1357 | //Templates - third iteration after fields have been created to set field context settings 1358 | foreach($data->templates as $np){ 1359 | $template = $this->templates->{$np->template}; 1360 | 1361 | if(isset($np->field_settings)) { 1362 | foreach($np->field_settings as $np_field_name => $np_field_value){ 1363 | $f = $template->fieldgroup->getField($np_field_name, true);//get the field in context of this template 1364 | foreach($np_field_value as $field_setting => $field_setting_value){ 1365 | $f->$field_setting = $field_setting_value;//value of the field 1366 | } 1367 | $this->fields->saveFieldgroupContext($f, $template->fieldgroup);//save new setting in context 1368 | } 1369 | } 1370 | } 1371 | 1372 | 1373 | //Pages 1374 | $i=0; 1375 | $top_parent_page = ''; 1376 | 1377 | if($this->session->import_components == 'everything'){ 1378 | 1379 | if(isset($data->pages)){ 1380 | foreach($data->pages as $np){ 1381 | //error_log('I:'.$i.':PN:'.$np->name.':PT:'.$np->page_template); 1382 | //if page is not in the list of selected import pages, then skip 1383 | if(is_array($this->session->import_pages) && in_array($np->name, $this->session->import_pages)) continue; 1384 | 1385 | $import_to_parent = $this->pages->get($this->session->import_to_parent); 1386 | $parent_path = str_replace("//", "/", $import_to_parent->path.$np->parent_name); 1387 | $parent = $this->pages->get($parent_path); 1388 | $parent_id = $parent->id; 1389 | if($np->parent_name=='' && ($np->name=='home' || $np->name=='')){ 1390 | $parent_id = 0; 1391 | } 1392 | if($parent_id == '') $parent_id = 1; 1393 | if($np->name=='') $np->name='home'; //sometimes the home page has no name in the exported json - multi-language only I think, although this should now be taken care of during export 1394 | 1395 | //if import_type is REPLACE then we need to delete all pages under the parent (hence the $i==0 check) so we end up with an exact copy of the imported site 1396 | //if($this->session->import_type == 'replace' && $i==0){ 1397 | //removed $i==0 because not all sub trees were being deleted - hopefully no side effects 1398 | if($this->session->import_type == 'replace'){ 1399 | $rps = $this->pages->get("name={$np->name}, template={$np->page_template}, parent=$parent_id, include=all, has_parent!=7")->find("include=all, id!=2, id!=7, has_parent!=2, has_parent!=7, template!=admin"); 1400 | //$rp = $this->pages->get("name={$np->name}, template={$np->page_template}, parent=$parent_id, include=all, has_parent!=7"); 1401 | 1402 | foreach($rps as $rp){ 1403 | if($rp->deleteable()) { 1404 | $this->pages->delete($rp, true); 1405 | } 1406 | } 1407 | 1408 | } 1409 | 1410 | $checkForPage = $this->pages->get("name={$np->name}, parent=$parent_id, include=all, has_parent!=7"); 1411 | 1412 | if($np->name=="home" || $np->name==""){ //this is for imports that include the home page. Apparently multi-language page names allow an empty home page name: https://processwire.com/talk/topic/4420-page-list-migrator/?p=65826 1413 | $wp = $this->pages->get("/"); 1414 | } 1415 | //elseif(!$this->pages->get("name={$np->name}, template={$np->page_template}, parent=$parent_id, include=all, has_parent!=7")->id){ //Check to see if a page with same name, template and parent already exists before creating it 1416 | //changed in case there is a page that has the same name and parent, but the new version has a different template - hopefully no side effects 1417 | // see note below where we set the new template for the page if it is different 1418 | //elseif(!$this->pages->get("name={$np->name}, parent=$parent_id, include=all, has_parent!=7")->id){ //Check to see if a page with same name, template and parent already exists before creating it 1419 | //elseif(!$checkForPage->id && $checkForPage->id != 0){ //Check to see if a page with same name, template and parent already exists before creating it 1420 | elseif(!$checkForPage->id){ // fix by @jlahijani: removed '&& $checkForPage->id != 0' because that doesn't make sense 1421 | $wp = new Page(); 1422 | $wp->parent = $this->pages->get($parent_id); 1423 | $wp->template = $this->templates->{$np->page_template}; 1424 | $wp->name = $np->name; 1425 | $wp->of(false); 1426 | $this->newPage = true; 1427 | } 1428 | else{ 1429 | //$wp = $this->pages->get("name={$np->name}, template={$np->page_template}, parent=$parent_id, include=all, has_parent!=7"); 1430 | //changed in case there is a page that has the same name and parent, but the new version has a different template - hopefully no side effects 1431 | // see note below where we set the new template for the page if it is different 1432 | $wp = $checkForPage; 1433 | } 1434 | 1435 | // this is for pages that already existed but now have a new template, so we need to change it. 1436 | // was initially added for dealing with http404 page since it can't be deleted. If the new version has a different template we need to change it. 1437 | // thanks to @jlahijani for reporting 1438 | if($this->session->import_type == 'overwrite' || $this->session->import_type == 'replace'){ 1439 | if($wp->template->name != $np->page_template) $wp->template = $this->templates->{$np->page_template}; 1440 | } 1441 | 1442 | $wp->of(false); 1443 | $wp->save(); 1444 | 1445 | //set multi-language page names 1446 | if(class_exists("LanguageSupportPageNames", false) && $this->languages) { 1447 | foreach($this->languages as $language){ 1448 | if(isset($np->{'name_'.$language->name.'_name'})){ 1449 | $lalias = $np->{'name_'.$language->name.'_name'}; 1450 | $lang = $this->languages->get($language->name); 1451 | $lalias_name = $this->sanitizer->pageName($lalias); 1452 | $wp->set("status$lang",$np->{'name_'.$language->name.'_status'}); 1453 | $wp->set("name$lang",$lalias_name); 1454 | $wp->save(); 1455 | } 1456 | } 1457 | } 1458 | 1459 | 1460 | if($i==0) $top_parent_page = $wp; //Used for link to show created page tree 1461 | 1462 | if($this->newPage || $this->session->import_type == "overwrite" || $this->session->import_type == "replace"){ 1463 | $wp->status = $np->status; 1464 | $wp->sort = $np->sort; 1465 | if(isset($np->sortfield)) $wp->sortfield = $np->sortfield; 1466 | 1467 | if(isset($np->data)) { 1468 | foreach($np->data as $np_field_name => $np_field_value){ 1469 | 1470 | //if field is not in the list of selected import fields, then skip 1471 | if(is_array($this->session->import_fields) && in_array($np_field_name, $this->session->import_fields)) continue; 1472 | 1473 | if(is_object($np_field_value) && property_exists($np_field_value, 'default')){ // this is for multi language versions of fields - the check for 'default' key should distinguish it from other fields that are objects 1474 | $this->modules->get("LanguageSupport"); // install language support module if it's not already installed. This shouldn't be necessary as it is does above. 1475 | foreach($np_field_value as $language => $field_value){ 1476 | $wp->$np_field_name->setLanguageValue($this->languages->get($language), $this->abstractedLinkEncoder($field_value)); 1477 | } 1478 | } 1479 | elseif(is_object($np_field_value)){ // this is for other custom fieldtypes with multiple DB fields 1480 | foreach($np_field_value as $field_name => $field_value){ 1481 | // if conditional is required because of float fields like in MapMarker Fieldtype, otherwise error trying to insert blank value 1482 | if($field_value!='') $wp->$np_field_name->$field_name = $field_value; 1483 | } 1484 | } 1485 | elseif($wp->fields->get($np_field_name) && $wp->fields->get($np_field_name)->type instanceof FieldtypeFile){ // this is for file/image fields 1486 | if(is_array($np_field_value)){ 1487 | foreach($np_field_value as $file) { 1488 | 1489 | $tmpImgDir = $this->page->filesManager()->path().'migratorfiles/pages/'.$this->removeParentFromPath($wp->path, $this->session->import_to_parent, 'import'); 1490 | $tempImgDir = str_replace('//', '/', $tmpImgDir); 1491 | 1492 | if(strpos($file->data,'//') === false) { //local images 1493 | $filepath = $tmpImgDir.$file->data; 1494 | } 1495 | else{ //images with full paths - probably from 3rd party migrator 1496 | $filepath = $file->data; 1497 | } 1498 | 1499 | if(file_exists($filepath) || strpos($filepath,'//') !== false){ 1500 | try { 1501 | $wp->$np_field_name->add($filepath); 1502 | 1503 | //This was here for trying to support rename on save using Custom Upload Names when images are embedded into RTE fields. 1504 | //Will revisit later 1505 | /*if($wp->fields->get($np_field_name) && $wp->fields->get($np_field_name)->type instanceof FieldtypeImage){ 1506 | 1507 | $dir = new DirectoryIterator($tmpImgDir); 1508 | foreach($dir as $tmpfile) { 1509 | if($tmpfile->isDir() || $tmpfile->isDot()) continue; 1510 | if($this->isImgVarOf(pathinfo($filepath, PATHINFO_BASENAME), pathinfo($tmpImgDir.$tmpfile, PATHINFO_BASENAME))){ 1511 | rename($tmpImgDir.$tmpfile, $wp->filesManager()->path() . $tmpfile); 1512 | } 1513 | } 1514 | }*/ 1515 | 1516 | if(strpos($filepath,'//') === false) unlink($filepath); //remove from migratorfiles temp folder so that it won't get re-copied with the variations a little further down 1517 | 1518 | $wp->of(false); 1519 | $wp->save($np_field_name); 1520 | $wp->$np_field_name->last()->description = $file->description; 1521 | if($this->fields->$np_field_name->useTags == 1) $wp->$np_field_name->last()->tags = $file->tags; 1522 | $wp->save($np_field_name); 1523 | 1524 | } catch (Exception $e) { 1525 | //image must not be available at remote URL 1526 | } 1527 | } 1528 | 1529 | //no longer needed as all additional versions of images are now copied across anyway 1530 | //copy all versions of FieldtypeCropImage images from the Thumbnails module into the final assets/files/id folder 1531 | /*$field = $wp->fields->get($np_field_name); 1532 | if($field->thumbSetting){ 1533 | $crops = $field->thumbSetting; 1534 | $crops_a = explode("\n", $crops); 1535 | foreach($crops_a as $crop) { 1536 | $crop = explode(',', $crop); 1537 | $name = wire('sanitizer')->name($crop[0]); 1538 | if(!strlen($name)) continue; 1539 | $cropFilename = $this->page->filesManager()->path().'migratorfiles/pages/'.$this->removeParentFromPath($wp->path, $this->session->import_to_parent, 'import') . $name . '_' . $file->data; 1540 | $cropFilename = str_replace('//', '/', $cropFilename); 1541 | if(is_file($cropFilename)) { 1542 | copy($cropFilename, $wp->filesManager()->path() . pathinfo($cropFilename, PATHINFO_BASENAME)); 1543 | } 1544 | } 1545 | }*/ 1546 | } 1547 | } 1548 | } 1549 | //All comments code thanks to Okeowo Aderemi 1550 | elseif($wp->fields->get($np_field_name) && $wp->fields->get($np_field_name)->type == 'FieldtypeComments'){ 1551 | //Save all the comments to the page 1552 | if(is_array($np_field_value)){ 1553 | //This iterates over the array of comments 1554 | $commentStatuses = array(); 1555 | $citem=0; 1556 | foreach($np_field_value as $comment) { 1557 | if(!is_numeric($comment->status)) continue; 1558 | if(class_exists('Comment')) { 1559 | $c = new Comment(); 1560 | } 1561 | else { 1562 | $c = new \ProcessWire\Comment(); 1563 | } 1564 | $wp->of(false); 1565 | //$c->id=$comment->id; 1566 | $c->text = $comment->data; 1567 | $c->cite = $comment->cite; 1568 | $c->email = $comment->email; 1569 | $c->created = $comment->comment_date; 1570 | $c->ip = $comment->ip; 1571 | $c->website = $comment->website; 1572 | //TODO: Not sure about this, but might be able to do something like it with the new comments function in PW 2.6 1573 | //$c->parent_id=$comment->parent_id; 1574 | 1575 | $wp->{$np_field_name}->add($c); 1576 | 1577 | $commentStatuses[$citem] = $comment->status ? $comment->status : 0; 1578 | 1579 | $citem++; 1580 | //$wp->save($np_field_name); 1581 | //$c->status = $comment->status ? $comment->status : 0; //need to set after saving to allow setting status without being subject to moderation settings 1582 | //$wp->comments->last()->status = $comment->status ? $comment->status : 0; 1583 | //$wp->save($np_field_name); 1584 | 1585 | // don't save here because it results in duplicate comments 1586 | // the main page save in the user details / page dates section takes care of the comments 1587 | } 1588 | } 1589 | } 1590 | elseif($wp->fields->get($np_field_name) && $wp->fields->get($np_field_name)->type == 'FieldtypePage'){ // this is for page fields 1591 | // these were breaking page fields and not needed because all of this is taken care of in the next loop further down 1592 | // need to grab the first (and only item) from the return array if the page field is a single type 1593 | /*if($wp->$np_field_name instanceof Page) { 1594 | $wp->$np_field_name = $this->getPageFieldIDFromName($np_field_value)[0]; 1595 | // else add the entire array to multi page field 1596 | } else if($wp->$np_field_name instanceof PageArray) { 1597 | $wp->$np_field_name = $this->getPageFieldIDFromName($np_field_value); 1598 | }*/ 1599 | } 1600 | elseif($wp->fields->get($np_field_name) && $wp->fields->get($np_field_name)->type == 'FieldtypeRepeater'){ // this is for repeater fields 1601 | $n=0; 1602 | foreach($np_field_value as $subfield => $valuearray){ 1603 | $newrf = $wp->$np_field_name->getNew(); // getNew() is special PW helper method for creating new repeater items (http://processwire.com/api/fieldtypes/repeaters/) 1604 | $newrf->save(); 1605 | $this->repeaterSubFields[] = $newrf; 1606 | $wp->save(); //needed for repeaters with file/image fields 1607 | $wp->of(false); 1608 | foreach($valuearray as $field => $value){ 1609 | if($wp->fields->get($field) && $wp->fields->get($field)->type instanceof FieldtypeFile){ // this is for file/image fields 1610 | foreach($value as $file) { 1611 | $filepath = $this->page->filesManager()->path().'migratorfiles/pages/'.$this->removeParentFromPath($wp->path, $this->session->import_to_parent, 'import').$np_field_name.'_'.$n.'/'.$file->basename; 1612 | $filepath = str_replace('//', '/', $filepath); 1613 | if(file_exists($filepath)){ 1614 | $newrf->$field->add($filepath); 1615 | unlink($filepath); //remove from migratorfiles temp folder so that it won't get re-copied with the variations a little further down 1616 | $newrf->of(false); 1617 | $newrf->save($field); 1618 | $newrf->$field->last()->description = $file->description; 1619 | if($this->fields->$field->useTags == 1) $newrf->$field->last()->tags = $file->tags; 1620 | $newrf->save($field); 1621 | } 1622 | } 1623 | } 1624 | else{ 1625 | $arr = (array)$value; 1626 | //check if object empty and set to empty string to prevent "Recoverable Fatal Error: Object of class stdClass could not be converted to string (line 38 of wire/modules/LanguageSupport/FieldtypeTextLanguage.module) " error 1627 | if(is_object($value) && empty($arr)) $value = ''; 1628 | if($this->fields->get($field)->type == 'FieldtypeImage') continue; //TODO This is a hack for @tobaco's test import - need to figure out actual problem with images in repeaters 1629 | $newrf->$field = $value; 1630 | $newrf->save(); 1631 | } 1632 | } 1633 | $wp->save(); 1634 | $n++; 1635 | } 1636 | } 1637 | elseif($wp->fields->get($np_field_name) && $wp->fields->get($np_field_name)->type == 'FieldtypeTextareas'){ // profields textareas 1638 | $items = explode("\r", $np_field_value); 1639 | foreach($items as $f => $v){ 1640 | if(empty($v) || !strpos($v, ':')) continue; 1641 | list($name, $v) = explode(":", $v, 2); 1642 | $wp->{$np_field_name}->$name = $v; 1643 | } 1644 | $wp->save($np_field_name); 1645 | } 1646 | elseif(is_array($np_field_value)){ 1647 | //for Table field types 1648 | if(count($np_field_value)>0 && is_object($np_field_value[0]) && $wp->fields->get($np_field_name)->type == 'FieldtypeTable'){ 1649 | $wp->of(false); 1650 | $wp->$np_field_name->removeAll(); 1651 | foreach($np_field_value as $entry){ 1652 | $tableentry = $wp->$np_field_name->makeBlankItem(); 1653 | foreach($entry as $field_name => $field_value){ 1654 | if($field_name != 'data') $tableentry->$field_name = $field_value; 1655 | } 1656 | $wp->$np_field_name->add($tableentry); 1657 | $wp->save($np_field_name); 1658 | } 1659 | } 1660 | //for other field types with arrays as values - so far I know Multiplier fits here, but there might be others 1661 | else { 1662 | if($wp->fields->get($np_field_name)->type != 'FieldtypeRepeaterMatrix') { 1663 | $wp->$np_field_name = $np_field_value; 1664 | } 1665 | // TODO - add support for RepeaterMatrix 1666 | } 1667 | } 1668 | //Textarea fields 1669 | elseif($wp->fields->get($np_field_name) && $wp->fields->get($np_field_name)->type == 'FieldtypeTextarea'){ 1670 | $wp->$np_field_name = $this->abstractedLinkEncoder($this->idImagePath($np_field_value, $wp)); 1671 | } 1672 | //should be just text fields left 1673 | else{ 1674 | $wp->$np_field_name = $np_field_value; 1675 | } 1676 | } 1677 | } 1678 | 1679 | 1680 | if($this->session->user_details == 1 || $this->session->page_dates == 1){ 1681 | //Set user details for page from export if the user_details option is selected. Quiet save is needed to allow this change 1682 | if($this->session->user_details == 1){ 1683 | if(isset($np->created_users_id)) $wp->created_users_id = $this->getUserIDFromName($np->created_users_id); 1684 | if(isset($np->modified_users_id)) $wp->modified_users_id = $this->getUserIDFromName($np->modified_users_id); 1685 | $wp->save(array('quiet' => true)); 1686 | } 1687 | 1688 | //Set modified/created date for page from export if the page_dates option is selected. Quiet save is needed to allow this change 1689 | if($this->session->page_dates == 1){ 1690 | // >0 is to check negative timestamp that seems to come when site's homepage is exported which creates this error 1691 | //SQLSTATE[22007]: Invalid datetime format: 1292 Incorrect datetime value: '5200-06-27 09:27:47' for column 'created' at row 1 1692 | if(isset($np->created) && $np->created > 0) $wp->created = $np->created; 1693 | if(isset($np->published) && $np->published > 0) $wp->published = $np->published; 1694 | if(isset($np->modified) && $np->modified > 0) $wp->modified = $np->modified; 1695 | $wp->save(array('quiet' => true)); 1696 | } 1697 | } 1698 | else{ 1699 | $wp->save(); 1700 | } 1701 | 1702 | if($this->fields->get($np_field_name)->type == 'FieldtypeComments') { 1703 | if(isset($commentStatuses) && $wp->id) { 1704 | $cpp = $this->pages->get($wp->id); 1705 | if($cpp->id && $cpp->{$np_field_name}) { 1706 | $citem=0; 1707 | foreach($commentStatuses as $comment => $status) { 1708 | $sql = "UPDATE field_{$np_field_name} SET status=$status WHERE pages_id=:pageid AND id=:commentid"; 1709 | $query = $this->wire('database')->prepare($sql); 1710 | $query->bindValue(':commentid', $cpp->{$np_field_name}->eq($citem)->id); 1711 | $query->bindValue(':pageid', $wp->id); 1712 | $query->execute(); 1713 | $citem++; 1714 | } 1715 | } 1716 | } 1717 | } 1718 | 1719 | // move all the remaining (should be just the variations) page files into the destination PW site's assets/files/id folder 1720 | //normal file/image fields 1721 | $srcDir = $this->session->migratorFilesDir . '/pages/' . $this->removeParentFromPath($wp->path, $this->session->import_to_parent, 'import'); 1722 | $srcDir = str_replace('//', '/', $srcDir); 1723 | $destDir = $wp->filesManager()->path(); 1724 | 1725 | if (file_exists($srcDir) && is_dir($srcDir) && $handle = opendir($srcDir)) { 1726 | while (false !== ($file = readdir($handle))) { 1727 | if (is_file($srcDir . $file)) { 1728 | rename($srcDir . $file, $destDir . $file); 1729 | } 1730 | } 1731 | closedir($handle); 1732 | } 1733 | 1734 | // repeater file/image fields 1735 | $n=0; 1736 | foreach($this->repeaterSubFields as $rsf){ 1737 | $srcDir = $this->session->migratorFilesDir . '/pages/' . $this->removeParentFromPath($wp->path, $this->session->import_to_parent, 'import').$np_field_name.'_'.$n.'/'; 1738 | $srcDir = str_replace('//', '/', $srcDir); 1739 | $destDir = $rsf->filesManager()->path(); 1740 | 1741 | if (file_exists($srcDir) && is_dir($srcDir) && $handle = opendir($srcDir)) { 1742 | while (false !== ($file = readdir($handle))) { 1743 | if (is_file($srcDir . $file)) { 1744 | rename($srcDir . $file, $destDir . $file); 1745 | } 1746 | } 1747 | closedir($handle); 1748 | } 1749 | $n++; 1750 | } 1751 | 1752 | } 1753 | 1754 | $i++; 1755 | 1756 | } 1757 | 1758 | } 1759 | 1760 | } 1761 | 1762 | //add parent_id to page fields now that the parent page has been created 1763 | foreach($pagefield_parent_ids as $pagefield_name => $pagefield_parent_id_name){ 1764 | $pagefield = $this->fields->get($pagefield_name); 1765 | $pagefield->parent_id = $this->pages->get("name=$pagefield_parent_id_name, include=all, has_parent!=7")->id; 1766 | $pagefield->save(); 1767 | } 1768 | 1769 | //add parent_id to pagetable fields now that the parent page has been created 1770 | foreach($pagetablefield_parent_ids as $pagetablefield_name => $pagetablefield_parent_id_name){ 1771 | $pagetablefield = $this->fields->get($pagetablefield_name); 1772 | $pagetablefield->parent_id = $pagetablefield_parent_id_name; 1773 | $pagetablefield->save(); 1774 | } 1775 | 1776 | //Pages second iteration 1777 | // run through pages again to populate page field data now that the page fields selectable pages and pagetable item child pages have been created 1778 | if($this->session->import_components == 'everything'){ 1779 | 1780 | if(isset($data->pages)){ 1781 | foreach($data->pages as $np){ 1782 | 1783 | /*$import_to_parent = $this->pages->get($this->session->import_to_parent); 1784 | $parent = $this->pages->get($import_to_parent->path.$np->parent_name.'/'); 1785 | if(!$parent->id) $parent = $this->pages->get('/');*/ 1786 | 1787 | /*$import_to_parent = $this->pages->get($this->session->import_to_parent); 1788 | $parent = $this->pages->get($import_to_parent->path.$np->parent_name);*/ 1789 | $import_to_parent = $this->pages->get($this->session->import_to_parent); 1790 | $parent_path = str_replace("//", "/", $import_to_parent->path.$np->parent_name); 1791 | $parent = $this->pages->get($parent_path); 1792 | $parent_id = $parent->id; 1793 | if($np->parent_name=='' && ($np->name=='home' || $np->name=='')){ 1794 | $parent_id = 0; 1795 | } 1796 | if($np->name=='') $np->name='home'; //sometimes the home page has no name in the exported json - multi-language thing I think 1797 | 1798 | 1799 | $cp = $this->pages->get("name={$np->name}, template={$np->page_template}, parent=$parent_id, include=all, has_parent!=7"); 1800 | if(!$cp->id) continue; 1801 | if(isset($np->data)) { 1802 | foreach($np->data as $np_field_name => $np_field_value){ 1803 | 1804 | //if field is not in the list of selected import fields, then skip 1805 | if(is_array($this->session->import_fields) && in_array($np_field_name, $this->session->import_fields)) continue; 1806 | 1807 | if($cp->fields->get($np_field_name) && $cp->fields->get($np_field_name)->type == 'FieldtypePage'){ // this is for page fields 1808 | $cp->of(false); 1809 | 1810 | $fieldid = $this->getPageFieldIDFromName($np_field_value, $np_field_name); 1811 | // need to grab the first [0] (and only item) from the return array if the page field is a single type 1812 | if($cp->$np_field_name instanceof Page || ($cp->$np_field_name && !get_class($cp->$np_field_name))) { // fix by @jlahijani: it can be classless 1813 | if(is_array($np_field_value) && !empty($np_field_value)) $cp->$np_field_name = $fieldid[0]; 1814 | // else add the entire array to multi page field 1815 | } else if($cp->$np_field_name instanceof PageArray) { 1816 | $cp->$np_field_name = $fieldid; 1817 | } 1818 | 1819 | $cp->save(); 1820 | } 1821 | elseif($cp->fields->get($np_field_name) && $cp->fields->get($np_field_name)->type == 'FieldtypePageTable'){ 1822 | foreach($np_field_value as $item){ 1823 | $items_parent_id = $cp->fields->get($np_field_name)->parent_id == 0 ? $cp->id : $cp->fields->get($np_field_name)->parent_id; 1824 | $ptp = $this->pages->get("parent={$items_parent_id}, name=$item"); 1825 | $cp->{$np_field_name}->add($ptp); 1826 | $cp->save($np_field_name); 1827 | } 1828 | } 1829 | } 1830 | } 1831 | $cp->of(false); 1832 | $cp->save(); 1833 | 1834 | // RE SET modified values - needs to be down here after the last page save so it doesn't get overwritten 1835 | if($this->session->user_details == 1 || $this->session->page_dates == 1){ 1836 | if($this->session->user_details == 1){ 1837 | if(isset($np->modified_users_id) && $np->modified_users_id != ''){ 1838 | //quiet mode doesn't work for modified so manually set this 1839 | $sql = "UPDATE `pages` SET `modified_users_id` = '".$this->getUserIDFromName($np->modified_users_id)."' WHERE `id` = '".$cp->id."';"; 1840 | $update = wire('db')->query($sql); 1841 | } 1842 | } 1843 | if($this->session->page_dates == 1){ 1844 | if(isset($np->modified) && $np->modified != ''){ 1845 | //quiet mode doesn't work for modified so manually set this 1846 | $sql = "UPDATE `pages` SET `modified` = '".date('Y-m-d H:i:s', $np->modified)."' WHERE `id` = '".$cp->id."';"; 1847 | $update = wire('db')->query($sql); 1848 | } 1849 | } 1850 | } 1851 | 1852 | } 1853 | } 1854 | } 1855 | 1856 | //remove the extracted folder of all the migrated files, templates, and json file 1857 | if($this->session->migratorFilesDir && file_exists($this->session->migratorFilesDir)) $this->recursiveDelete($this->session->migratorFilesDir); 1858 | 1859 | return $this->processImportForm2Markup($i, $top_parent_page != '' ? $top_parent_page->id : false); 1860 | } 1861 | 1862 | 1863 | /** 1864 | * Process the "Restore Step 2" form and restore the database backup file 1865 | * 1866 | */ 1867 | protected function processRestoreForm2(InputfieldForm $form) { 1868 | 1869 | $backup = new WireDatabaseBackup($this->config->paths->assets.'migratorbackups/' . $this->input->post->restore_directory . '/'); 1870 | $backup->setDatabase($this->database); 1871 | $backup->setDatabaseConfig($this->config); 1872 | 1873 | $success = $backup->restore($this->config->paths->assets.'migratorbackups/' . $this->input->post->restore_directory . '/migratorbackup.sql', array('dropAll' => true)); 1874 | if($success) $this->message("Database successfully restored."); 1875 | else $this->error("Sorry, there was a problem and the database could not be restored."); 1876 | 1877 | $this->recursiveDelete($this->config->paths->templates, false); //clean up templates folder so it will only contain those files from the backup 1878 | $this->recursiveDelete($this->config->paths->files, false); //clean up files folder so it will only contain those files from the backup 1879 | 1880 | if(wireCopy($this->config->paths->assets.'migratorbackups/' . $this->input->post->restore_directory . '/templates/', $this->config->paths->templates, true)){ 1881 | $this->message("Templates directory successfully restored."); 1882 | } 1883 | else{ 1884 | $this->error("Sorry, there was a problem and the templates directory could not be restored."); 1885 | } 1886 | 1887 | if(wireCopy($this->config->paths->assets.'migratorbackups/' . $this->input->post->restore_directory . '/files/', $this->config->paths->files, true)){ 1888 | $this->message("Assets/Files directory successfully restored."); 1889 | } 1890 | else{ 1891 | $this->error("Sorry, there was a problem and the Assets/Files directory could not be restored."); 1892 | } 1893 | 1894 | unlink($this->config->paths->templates . 'migratorbackup.sql'); 1895 | 1896 | $this->session->redirect("../"); 1897 | 1898 | } 1899 | 1900 | 1901 | 1902 | /** 1903 | * Push all relevant templates, fields and pages to the pageToArray function and then JSON encode 1904 | * 1905 | */ 1906 | protected function pagesToJSON(PageArray $items, $export_components) { 1907 | 1908 | $a = array(); 1909 | $pages_array = array(); 1910 | $templates_array = array(); 1911 | $fields_array = array(); 1912 | $current_template = array(); 1913 | $i=0; 1914 | foreach($items as $item) { 1915 | 1916 | //Pages 1917 | if($export_components != 'fields_and_templates_only'){ 1918 | if($export_components == 'everything' || ($export_components == 'fields_templates_and_structural_pages' && count($item->siblings("children.count>0"))>0)){ 1919 | if(!in_array($item->path, $pages_array)){ 1920 | $a['pages'][] = $this->pageToArray($item, 'pages', null, $i); 1921 | $pages_array[] = $item->path; 1922 | } 1923 | } 1924 | } 1925 | 1926 | //Templates 1927 | if(!in_array($item->template->name, $templates_array)){ 1928 | $a['templates'][] = $this->pageToArray($item, 'templates', null, $i); 1929 | $templates_array[] = $item->template->name; 1930 | $this->templateFiles[] = $this->templates->get($item->template->name)->filename; 1931 | } 1932 | 1933 | //Fields 1934 | foreach($this->templates->get($item->template->name)->fields as $field){ 1935 | // suppressing notices (@) because I removed "array_key_exists($item->template->name, $current_template) && " because it was preventing creation of the same fields again in subsequent templates although I don't know why - seems like it should work 1936 | if(!in_array($field->name, $fields_array) || !isset($current_template[$item->template->name]) || (isset($current_template[$item->template->name]) && !in_array($field->name, $current_template[$item->template->name]))){ 1937 | // if it's a repeater field then need to look through its subfields and add those to array if not already present 1938 | if($field->type=="FieldtypeRepeater"){ 1939 | foreach($field->repeaterFields as $repeaterField){ 1940 | $repeater_subfield = $this->fields->get($repeaterField); 1941 | if(!in_array($repeater_subfield->name, $fields_array)){ 1942 | $a['fields'][] = $this->pageToArray($repeater_subfield, 'fields', null, $i); 1943 | $fields_array[] = $repeater_subfield->name; 1944 | } 1945 | } 1946 | } 1947 | 1948 | // if it's a page field (and the parent_id is defined) then need to add the selectable pages, templates, and fields to array if not already present 1949 | // the reason we require the parent_id to be set is that it doesn't make sense to migrate a collection of pages from all over a page tree - would be a mess 1950 | // also don't want to export the entire page tree if the page field has Home (ID:1) as the parent of selectable pages 1951 | if($field->parent_id!="1" && $field->parent_id!="" && $field->parent_id!=0 && ($field->type=="FieldtypePage" || $field->type=="FieldtypePageTable")){ 1952 | 1953 | //initial check to see if there are any pagefield selectable pages that actually need including if the changes_since date is set 1954 | if($this->session->changes_since != ''){ 1955 | $parent_page = $this->pages->get("modified>{$this->session->changes_since}, id={$field->parent_id}"); 1956 | if($parent_page != '') $child_pages = $parent_page->children("modified>{$this->session->changes_since}, include=all, has_parent!=7"); 1957 | if($parent_page == '' && (!isset($child_pages) || $child_pages == '')) continue; 1958 | } 1959 | 1960 | if($field->type=="FieldtypePageTable"){ 1961 | $selectablePages = $this->pages->get($field->parent_id)->children("include=all, has_parent!=7"); 1962 | } 1963 | elseif($field->type=="FieldtypePage"){ 1964 | $inputfield = $field->getInputfield($this->page); 1965 | $selectablePages = $inputfield->getSelectablePages($this->page); 1966 | } 1967 | 1968 | foreach($selectablePages as $selectablePage){ 1969 | 1970 | foreach($selectablePage->fields as $selectablePageField){ 1971 | 1972 | $page_selectable_field = $selectablePageField; 1973 | 1974 | if($field->template_id != ""){ 1975 | $pagefield_template = $this->templates->get(is_array($field->template_id) ? $field->template_id[0] : $field->template_id); // PageTable fields template setting is an array, hence the [0] 1976 | } 1977 | else{ 1978 | $pagefield_template = $selectablePage->template; 1979 | } 1980 | 1981 | // pagefield parent page 1982 | if($this->session->changes_since != ''){ 1983 | $parent_page = $this->pages->get("modified>{$this->session->changes_since}, id={$field->parent_id}, include=all, has_parent!=7"); 1984 | } 1985 | else{ 1986 | $parent_page = $this->pages->get("id={$field->parent_id}, include=all, has_parent!=7"); 1987 | } 1988 | if(!in_array($parent_page->name, $pages_array)){ //check to see if this page is not already in the array of pages being exported 1989 | $a['pages'][] = $this->pageToArray($parent_page, 'pages', null, $i); // this needs to be changed to 1 if we want to set the original parent, but this has problems too - not sure of the best solution for this yet. 1990 | $pages_array[] = $parent_page->name; 1991 | } 1992 | 1993 | // pagefield child pages 1994 | if($this->session->changes_since != ''){ 1995 | $child_pages = $parent_page->children("modified>{$this->session->changes_since}, include=all, has_parent!=7"); 1996 | } 1997 | else{ 1998 | $child_pages = $parent_page->children("include=all, has_parent!=7"); 1999 | } 2000 | foreach($child_pages as $child_page){ 2001 | if(!in_array($child_page->name, $pages_array)){ 2002 | $a['pages'][] = $this->pageToArray($child_page, 'pages', null, 1); // 1 is forced to ensure parent_name is not set to blank in pageToArray function - could be anything here but 0 2003 | $pages_array[] = $child_page->name; 2004 | } 2005 | } 2006 | 2007 | // pagefield parent and child templates 2008 | if(!in_array($pagefield_template->name, $templates_array)){ 2009 | $a['templates'][] = $this->pageToArray($parent_page, 'templates', null, $i); 2010 | $a['templates'][] = $this->pageToArray($child_page, 'templates', null, $i); 2011 | $templates_array[] = $pagefield_template->name; 2012 | } 2013 | 2014 | // pagefield parent fields 2015 | foreach($parent_page->template->fields as $pagefield_parent_field){ 2016 | if(!in_array($pagefield_parent_field->name, $fields_array)){ 2017 | $a['fields'][] = $this->pageToArray($pagefield_parent_field, 'fields', $parent_page->template->name, $i); 2018 | $fields_array[] = $pagefield_parent_field->name; 2019 | } 2020 | } 2021 | 2022 | // pagefield child fields 2023 | if(!in_array($page_selectable_field->name, $fields_array)){ 2024 | $a['fields'][] = $this->pageToArray($page_selectable_field, 'fields', $pagefield_template->name, $i); 2025 | $fields_array[] = $page_selectable_field->name; 2026 | } 2027 | } 2028 | } 2029 | } 2030 | 2031 | $a['fields'][] = $this->pageToArray($field, 'fields', $item->template->name, $i); 2032 | $fields_array[] = $field->name; 2033 | 2034 | 2035 | } 2036 | $current_template[$item->template->name][] = $field->name; // this seems a little ugly - uncomment the print_r below to see the array - way more repetition than needed 2037 | } 2038 | $i++; 2039 | 2040 | } 2041 | //print_r($current_template);exit; 2042 | 2043 | if($this->session->save_or_copy == 'save'){ 2044 | //Create zip of attached files 2045 | $allfiles = array(); 2046 | $i=0; 2047 | foreach($this->pageFiles as $filespath => $pagepath){ 2048 | //foreach($fields as $files){ 2049 | //foreach($files as $file){ 2050 | //error_log('FP:'.$filespath.':'.$pagepath); 2051 | 2052 | /*foreach (glob($filespath.'*.*') as $file){ 2053 | $allfiles[$i]['newpath'] = 'pages' . $pagepath . pathinfo($file, PATHINFO_BASENAME); 2054 | $allfiles[$i]['currentpath'] = $file; 2055 | $i++; 2056 | }*/ 2057 | 2058 | $dir = new DirectoryIterator($filespath); 2059 | foreach($dir as $file) { 2060 | if($file->isDir() || $file->isDot()) continue; 2061 | $allfiles[$i]['newpath'] = 'pages' . $pagepath . $file->getBasename(); 2062 | $allfiles[$i]['currentpath'] = $file->getPathname(); 2063 | $i++; 2064 | } 2065 | 2066 | 2067 | //} 2068 | } 2069 | 2070 | $this->create_zip($allfiles,$this->page->filesManager()->path().'files.zip', 'files'); 2071 | 2072 | //Add required template files to zip 2073 | $allfiles = array(); 2074 | foreach($this->templateFiles as $file){ 2075 | $allfiles[$file]['newpath'] = 'templates/' . pathinfo($file, PATHINFO_BASENAME); 2076 | $allfiles[$file]['currentpath'] = $file; 2077 | } 2078 | 2079 | if($this->input->helper_files != ''){ 2080 | //Add additional helper files as defined during export 2081 | foreach($this->input->helper_files as $helperfile){ 2082 | $pathFromTemplatesDir = str_replace($this->config->paths->templates,'',$helperfile); 2083 | $allfiles[$helperfile]['newpath'] = 'templates/' . $pathFromTemplatesDir; 2084 | $allfiles[$helperfile]['currentpath'] = $helperfile; 2085 | } 2086 | } 2087 | 2088 | $this->create_zip($allfiles,$this->page->filesManager()->path().'files.zip', 'files'); 2089 | } 2090 | 2091 | return json_encode($a); 2092 | } 2093 | 2094 | 2095 | /** 2096 | * Prepare arrays to convert to JSON 2097 | * 2098 | */ 2099 | protected function pageToArray($wp, $type, $template_name = null, $pageNum = 0) { 2100 | 2101 | if($type == 'pages'){ 2102 | 2103 | if($wp->name=='') $wp->name = 'home'; //sometimes the home page has no name in the exported json - multi-language only I think 2104 | 2105 | $data = array('name' => $wp->name); 2106 | 2107 | if(class_exists("LanguageSupportPageNames", false)) { 2108 | foreach($this->languages as $language){ 2109 | $data['name_'.$language->name.'_name'] = $wp->localName($language->name); 2110 | $data['name_'.$language->name.'_status'] = $wp->{'status'.$language->id}; 2111 | } 2112 | } 2113 | $treeParentPath = $this->pages->get($this->session->treeParent)->path; 2114 | 2115 | $data['parent_name'] = $pageNum == 0 ? '' : $this->removeParentFromPath($wp->path, $wp->parent->id, 'export'); 2116 | $data['page_template'] = $wp->template->name; 2117 | $data['status'] = $wp->status; 2118 | $data['sort'] = $wp->sort; 2119 | $data['sortfield'] = $wp->sortfield; 2120 | $data['created_users_id'] = $wp->createdUser->name.":".implode('|',array_map(array($this, 'getRoleNameFromID'), explode('|',$wp->createdUser->roles))); 2121 | $data['modified_users_id'] = $wp->modifiedUser->name.":".implode('|',array_map(array($this, 'getRoleNameFromID'), explode('|',$wp->modifiedUser->roles))); 2122 | $data['created'] = $wp->created; 2123 | $data['published'] = $wp->published; 2124 | $data['modified'] = $wp->modified; 2125 | $data['data'] = array(); 2126 | 2127 | foreach($wp->template->fieldgroup as $field) { 2128 | 2129 | $value = $wp->get($field->name); 2130 | $final_value = $field->type->sleepValue($wp, $field, $value); 2131 | if(is_array($final_value)){ 2132 | if($field->type instanceof FieldtypeFile){ //file and image fields 2133 | $data['data'][(string) $field] = $final_value; 2134 | //add files/images to $pageFiles array for later inclusion in export zip 2135 | //if(!empty($final_value)) $this->pageFiles[$wp->path][] = $this->getFullNamedPath($final_value, $field, $wp); 2136 | if(!empty($final_value)) $this->pageFiles[$wp->filesManager()->path()] = $wp->path; 2137 | } 2138 | elseif(strpos($field->type, 'Language') !== false) { 2139 | $data['data'][(string) $field] = $this->getLanguageNameFromID($this->abstractedLinkDecoder($final_value)); 2140 | } 2141 | elseif($field->type == "FieldtypePage" || $field->type == "FieldtypePageTable"){ 2142 | $data['data'][(string) $field] = $this->getPageFieldNameFromID($final_value); 2143 | } 2144 | //if(array_key_exists('data', $final_value) && array_key_exists('count', $final_value) && array_key_exists('parent_id', $final_value) && $this->modules->isInstalled("LanguageSupport")) { // the data, count and parent_id checks together should limit this to repeater field data 2145 | elseif($field->type == "FieldtypeRepeater"){ 2146 | $data['data'][(string) $field] = $this->getRepeaterContentFromIDs($this->abstractedLinkDecoder($final_value), $field, $wp); 2147 | } 2148 | /*elseif(array_key_exists('default', $final_value)){ // I think this should be just multilanguage versions of field data - is there a better way to check? 2149 | $data['data'][(string) $field] = $this->getLanguageNameFromID($this->abstractedLinkDecoder($final_value)); 2150 | }*/ 2151 | elseif(array_key_exists('data', $final_value)){ // I think this should be all the remaining custom field types with multiple DB fields, like MapMarker etc 2152 | //override $final_value array with the module names of the subfields, rather than the DB field names 2153 | $final_value = array(); 2154 | foreach($wp->{$field->name} as $subfield => $value){ 2155 | $final_value[$subfield] = $value; 2156 | } 2157 | $data['data'][(string) $field] = $final_value; 2158 | } 2159 | else{ //for other field types with arrays as the value - so far I know Multiplier fits here, but there might be others 2160 | $data['data'][(string) $field] = $final_value; 2161 | } 2162 | } 2163 | else { 2164 | $data['data'][(string) $field] = $this->nameImagePathId($this->abstractedLinkDecoder($final_value), $wp->path); 2165 | } 2166 | 2167 | } 2168 | 2169 | } 2170 | 2171 | 2172 | if($type == 'templates'){ 2173 | 2174 | $data = array( 2175 | 'template' => $wp->template->name, 2176 | ); 2177 | 2178 | //template context field settings 2179 | foreach($wp->template->fields as $tpl_field){ 2180 | foreach($wp->template->fieldgroup->getFieldContextArray($tpl_field->id) as $field_setting => $value){ 2181 | $data['field_settings'][$tpl_field->name][$field_setting] = $value; 2182 | } 2183 | } 2184 | 2185 | foreach($wp->template->getArray() as $field => $value) { 2186 | //Roles (Access Tab) 2187 | if(stripos($field,'Roles') !== false && (is_array($value) && !empty($value) && $value[0] != 0)) { 2188 | $names = array_map(array($this, 'getRoleNameFromID'), $value); 2189 | $data['data'][$field] = $names; 2190 | } 2191 | //Child and Parent template settings (Family Tab) 2192 | elseif(is_array($value) && !empty($value) && $value[0] != 0) { //Last check to hopefully deal with an error when childTemplates or parentTemplates somehow ended up as [0] => 0 2193 | $names = array_map(array($this, 'getTemplateNameFromID'), $value); 2194 | $data['data'][$field] = $names; 2195 | } 2196 | else{ 2197 | $data['data'][$field] = $value; 2198 | } 2199 | } 2200 | 2201 | } 2202 | 2203 | 2204 | if($type == 'fields'){ 2205 | 2206 | $data = array( 2207 | 2208 | 'name' => $wp->name, 2209 | 'label' => $wp->label, 2210 | 'description' => $wp->description, 2211 | 'template' => $template_name, 2212 | 'flags' => $wp->flags, 2213 | 'type' => "{$wp->type}", 2214 | 2215 | ); 2216 | 2217 | foreach($wp->getArray() as $field => $value) { 2218 | 2219 | //if(is_array($value) && !empty($value) && $value[0] != 0) { //Last check to hopefully deal with an error when childTemplates or parentTemplates somehow ended up as [0] => 0 2220 | if(is_array($value) && !empty($value) && (array_key_exists(0, $value) && $value[0] != 0)) { //Last check to hopefully deal with an error when childTemplates or parentTemplates somehow ended up as [0] => 0 2221 | $names = array_map(array($this, 'getFieldNameFromID'), $value); 2222 | $data['data'][$field] = $names; 2223 | } 2224 | else{ 2225 | if(strpos($wp->type, 'Language') !== false && strpos($field, 'label') !== false && $field != 'label') { //last check to make sure it ignores any field that might be named exactly 'label' 2226 | $language_id = preg_replace("/[^0-9]/", "", $field); 2227 | $data['data']['label_'.$this->languages->get($language_id)->name] = $value; 2228 | } 2229 | elseif($field == "parent_id"){ // convert page id to name for page field selectable parent 2230 | $data['data'][$field] = $this->pages->get("id={$value}, include=all, has_parent!=7")->name; 2231 | } 2232 | elseif($field == "template_id"){ // convert template id to name for page field selectable template 2233 | $data['data'][$field] = $this->templates->get($value)->name; 2234 | } 2235 | else{ 2236 | $data['data'][$field] = $value; 2237 | } 2238 | } 2239 | } 2240 | 2241 | } 2242 | return $data; 2243 | } 2244 | 2245 | 2246 | 2247 | 2248 | /** 2249 | * Provide the completion output markup for processImportForm2 2250 | * 2251 | */ 2252 | protected function processImportForm2Markup($numImported, $imported_parent_id) { 2253 | $out = ''; 2254 | if($imported_parent_id){ 2255 | $out .= "

Created all required templates and fields and imported/edited $numImported pages

" . 2256 | "

View the imported page tree

"; 2257 | } 2258 | else{ 2259 | $out .= "

Created all required templates and fields

"; 2260 | } 2261 | 2262 | if(count($this->migratedTemplateFileNames)>0){ 2263 | $out .= "

The following template files were migrated. If any of them contain includes that aren't in this list as well, you'll need to manually copy these across to this new PW install

"; 2264 | foreach($this->migratedTemplateFileNames as $templateFileName) $out .= $templateFileName.'
'; 2265 | } 2266 | 2267 | $out .= "

Import more content

"; 2268 | 2269 | return $out; 2270 | } 2271 | 2272 | 2273 | 2274 | /** 2275 | * Add a submit button, moved to a function so we don't have to do this several times 2276 | * 2277 | */ 2278 | protected function addSubmit(InputfieldForm $form, $value = 'Submit') { 2279 | $f = $this->modules->get("InputfieldSubmit"); 2280 | $f->name = 'submit'; 2281 | $f->value = $value; 2282 | $form->add($f); 2283 | } 2284 | 2285 | 2286 | /** 2287 | * Remove the parent section from the URL path of a page 2288 | * 2289 | */ 2290 | protected function removeParentFromPath($path, $parent, $importOrExport){ 2291 | 2292 | $parent = $this->pages->get("id={$parent}, include=all, has_parent!=7"); 2293 | if($importOrExport == 'import'){ 2294 | $returned_path = str_replace($parent->path, '/', $path) . '/'; 2295 | $returned_path = str_replace('//', '/', $returned_path); 2296 | $returned_path = str_replace('/home/', '', $returned_path); 2297 | return $returned_path; 2298 | } 2299 | else{ 2300 | //error_log('PN:'.$parent->name); 2301 | //error_log('PP:'.$parent->path); 2302 | //error_log('P:'. $path); 2303 | 2304 | $treeParentName = $parent->name == 'home' ? '/' : $parent->name; 2305 | //error_log('TPN:'.$treeParentName); 2306 | 2307 | $fullpath = strstr($path, $treeParentName); 2308 | //error_log('FP:'.$fullpath); 2309 | 2310 | $parts = explode('/', rtrim($fullpath, "/")); 2311 | array_pop($parts); //removes the name of the current page from the path 2312 | return implode('/', $parts) . '/'; 2313 | 2314 | //very old 2315 | /*$parts = explode('/', substr($path, 1)); 2316 | if(count($parts)>2) array_shift($parts); 2317 | return str_replace('home','',implode('/', $parts));*/ 2318 | //return preg_replace("/","",$path, 1); 2319 | 2320 | //last used 2321 | //return ltrim($path, '/'); 2322 | } 2323 | 2324 | /*if($importOrExport == 'import' && $this->pages->get($parent)->path == '/'){ 2325 | return $path; 2326 | } 2327 | else{ 2328 | $parts = explode('/', substr($path, 1)); 2329 | if(count($parts)>2) array_shift($parts); 2330 | error_log('RETURN:'.str_replace('home','',implode('/', $parts))); 2331 | return str_replace('home','',implode('/', $parts)); 2332 | }*/ 2333 | } 2334 | 2335 | 2336 | /** 2337 | * Return user ID from their username 2338 | * 2339 | */ 2340 | public function getUserIDFromName($userdetails){ 2341 | //check to see if the user already exists in destination PW install. If not, add the new user and return its ID 2342 | 2343 | $userRoles = str_replace(':','',strstr($userdetails, ':')); 2344 | $name = str_replace(':','',str_replace($userRoles, '', $userdetails)); 2345 | 2346 | if($name != '' && $this->users->get($name)->id){ 2347 | return $this->users->get($name)->id; 2348 | } 2349 | elseif($userRoles != ''){ 2350 | $newuser = new User(); 2351 | $newuser->name= $name; 2352 | foreach(explode('|',$userRoles) as $userRole) $this->addCreateRole($newuser, $userRole); 2353 | $newuser->save(); 2354 | return $newuser->id; 2355 | } 2356 | } 2357 | 2358 | 2359 | public function addCreateRole($newuser, $userrole){ 2360 | //check to see if the role already exists in destination PW install. If not, add the new role and add it to the supplied user 2361 | if(!$this->roles->get($userrole)->id){ 2362 | $newrole = new Role(); 2363 | $newrole->name= $userrole; 2364 | $newrole->save(); 2365 | $newuser->addRole($newrole); 2366 | } 2367 | $newuser->addRole($userrole); 2368 | } 2369 | 2370 | 2371 | /** 2372 | * Return template name from its ID 2373 | * 2374 | */ 2375 | public function getTemplateNameFromID($id){ 2376 | if($this->templates->get($id)) return $this->templates->get($id)->name; 2377 | } 2378 | 2379 | /** 2380 | * Return role name from its ID 2381 | * 2382 | */ 2383 | public function getRoleNameFromID($id){ 2384 | if($this->roles->get($id)) return $this->roles->get($id)->name; 2385 | } 2386 | 2387 | /** 2388 | * Return role ID from its name 2389 | * 2390 | */ 2391 | public function getRoleIDFromName($name){ 2392 | //check to see if the role already exists in destination PW install. If not, add the new role and return its ID 2393 | if($name != '' && $this->roles->get($name)->id){ 2394 | return $this->roles->get($name)->id; 2395 | } 2396 | else{ 2397 | //return $this->roles->get("guest")->id; 2398 | $newrole = new Role(); 2399 | $newrole->name= $name; 2400 | $newrole->save(); 2401 | return $newrole->id; 2402 | } 2403 | } 2404 | 2405 | /** 2406 | * Return field name from its ID 2407 | * 2408 | */ 2409 | public function getFieldNameFromID($id){ 2410 | if($this->fields->get($id)) return $this->fields->get($id)->name; 2411 | } 2412 | 2413 | /** 2414 | * Return field ID from its name 2415 | * 2416 | */ 2417 | public function getFieldIDFromName($name){ 2418 | if($this->fields->get($name)) return $this->fields->get($name)->id; 2419 | } 2420 | 2421 | /** 2422 | * Return template ID from its name 2423 | * 2424 | */ 2425 | public function getTemplateIDFromName($name){ 2426 | if($name != '' && $this->templates->get($name)) return $this->templates->get($name)->id; 2427 | } 2428 | 2429 | 2430 | public function isImgVarOf($origImage, $compareImage){ 2431 | $re = '/^' . 2432 | pathinfo($origImage, PATHINFO_FILENAME) . '\.' . // myfile. 2433 | '(\d+)x(\d+)' . // 50x50 2434 | '([pd]\d+x\d+|[a-z]{1,2})?' . // nw or p30x40 or d30x40 2435 | '\.' . pathinfo($origImage, PATHINFO_EXTENSION) . // .ext 2436 | '$/'; 2437 | 2438 | if(preg_match($re, $compareImage)) { 2439 | return true; 2440 | } 2441 | } 2442 | 2443 | 2444 | /** 2445 | * Return repeater field content from its array of IDs 2446 | * 2447 | */ 2448 | public function getRepeaterContentFromIDs( $array, $field, $wp ){ 2449 | $newArray = array(); 2450 | foreach($array as $key=>$value) { 2451 | if($key == 'data'){ 2452 | $i=0; 2453 | foreach(explode(',',$value) as $pageid){ 2454 | $fieldpage = $this->pages->get($pageid); 2455 | if(!$fieldpage->template) continue; //checks to see if there are actually any repeater content items stored for the page 2456 | foreach($fieldpage->template->fields as $repeaterField){ 2457 | if($repeaterField->type instanceof FieldtypeFile){ 2458 | $this->pageFiles[$fieldpage->filesManager()->path()] = $wp->path.$field->name.'_'.$i.'/'; 2459 | foreach($fieldpage->{$repeaterField->name} as $file){ 2460 | $newArray[$i][$repeaterField->name][] = $file->getArray(); 2461 | } 2462 | } 2463 | else{ 2464 | $newArray[$i][$repeaterField->name] = $fieldpage->{$repeaterField->name}; 2465 | } 2466 | } 2467 | $i++; 2468 | } 2469 | } 2470 | } 2471 | return $newArray; 2472 | } 2473 | 2474 | /** 2475 | * Return named path for files based on the path of the page and its filename ($value['data']) 2476 | * 2477 | */ 2478 | /*public function getFullNamedPath( $array, $field, $wp ){ 2479 | $newArray = array(); 2480 | 2481 | foreach($array as $value) { 2482 | $newArray[] = $wp->filesManager()->path() . $value['data']; 2483 | 2484 | //add all versions of FieldtypeCropImage images from the Thumbnails module 2485 | if($field->thumbSetting){ 2486 | $crops = $field->thumbSetting; 2487 | $crops_a = explode("\n", $crops); 2488 | foreach($crops_a as $crop) { 2489 | $crop = explode(',', $crop); 2490 | $name = wire('sanitizer')->name($crop[0]); 2491 | if(!strlen($name)) continue; 2492 | $cropFilename = $wp->filesManager()->path() . $name . '_' . $value['data']; 2493 | if(is_file($cropFilename)) { 2494 | $newArray[] = $cropFilename; 2495 | } 2496 | } 2497 | } 2498 | } 2499 | 2500 | return $newArray; 2501 | }*/ 2502 | 2503 | /** 2504 | * Return language name from its ID 2505 | * 2506 | */ 2507 | public function getLanguageNameFromID( $array ){ 2508 | $newArray = array(); 2509 | 2510 | foreach($array as $key=>$value) { 2511 | $language_id = preg_replace("/[^0-9]/", "", $key); //strips out 'data' from the language name leaving just the language id 2512 | $newArray[$language_id ? $this->languages->get($language_id)->name : 'default'] = $this->abstractedLinkDecoder($value); 2513 | } 2514 | 2515 | return $newArray; 2516 | } 2517 | 2518 | 2519 | /** 2520 | * Return page name from its ID - used for page field selected items 2521 | * 2522 | */ 2523 | public function getPageFieldNameFromID( $array ){ 2524 | $newArray = array(); 2525 | 2526 | foreach($array as $value) { 2527 | $newArray[] = $this->pages->get($value)->name; 2528 | } 2529 | 2530 | return $newArray; 2531 | } 2532 | 2533 | /** 2534 | * Return page ID from its name - used for page field selected items 2535 | * 2536 | */ 2537 | public function getPageFieldIDFromName( $array, $fieldname ){ 2538 | 2539 | //make sure we are getting a page from the pagefields available page parent / template 2540 | $pagefield = $this->fields->get($fieldname); 2541 | $pf_parent_id = $pagefield->parent_id ? ", parent={$pagefield->parent_id}" : ""; 2542 | $pf_template_id = $pagefield->template_id ? ", template={$pagefield->template_id}" : ""; 2543 | 2544 | $newArray = array(); 2545 | foreach($array as $value) { 2546 | $newArray[] = $this->pages->get("name={$value}{$pf_parent_id}{$pf_template_id}, include=all, has_parent!=7")->id; 2547 | } 2548 | 2549 | return $newArray; 2550 | } 2551 | 2552 | 2553 | /** 2554 | * Returns field with embedded links converted to format for compatibility with PageLinkAbstractor module (for import process) 2555 | * 2556 | */ 2557 | public function abstractedLinkEncoder($value) { 2558 | 2559 | if(!$this->modules->get("PageLinkAbstractor")) return $value; 2560 | 2561 | $rootUrl = $this->config->urls->root; 2562 | 2563 | // check if the value has links by looking for equals sign and quote 2564 | if(strpos($value, '="') || strpos($value, "='") || strpos($value, "=$rootUrl")) { 2565 | 2566 | // replace root url with tag, should work with images or tags 2567 | $value = preg_replace('{(=["\']?)' . preg_quote($rootUrl) . '}', '$1{~root_url}', $value); 2568 | 2569 | // replace page URLs 2570 | if(preg_match_all('|\{~root_url\}([-_./a-zA-Z0-9]+)|', $value, $matches)) { 2571 | $assetsUrl = $this->config->urls->assets; 2572 | foreach($matches[1] as $key => $url) { 2573 | if(strpos($url, $assetsUrl) !== false) continue; 2574 | if(strpos($url, "{~root_url}") === false) continue; // exclude external urls 2575 | $p = $this->pages->get("/$url"); 2576 | if($p->id) $value = str_replace($matches[0][$key], '{~page_' . $p->id . '_url}', $value); 2577 | } 2578 | } 2579 | 2580 | } 2581 | 2582 | return $value; 2583 | 2584 | } 2585 | 2586 | /** 2587 | * Returns RTE field with links to embedded images renamed to replace page ID with the path of the page so it can be converted on import at destination PW install 2588 | * 2589 | */ 2590 | public function nameImagePathId($html, $pagepath){ 2591 | if (strpos($html,'loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); 2594 | foreach ($dom->getElementsByTagName('img') as $img) { 2595 | $img->setAttribute( 'src', $pagepath . pathinfo($img->getAttribute('src'), PATHINFO_BASENAME)); 2596 | } 2597 | return preg_replace('/^/', '', str_replace( array('', '', '', ''), array('', '', '', ''), $dom->saveHTML())); 2598 | } 2599 | 2600 | 2601 | /** 2602 | * Returns RTE field with links to embedded images renamed to replace the path of the page with the assets/files/xxx so it will work at destination PW install 2603 | * 2604 | */ 2605 | public function idImagePath($html, $wp){ 2606 | if (strpos($html,'loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); 2609 | $imagesField = ''; 2610 | foreach($dom->getElementsByTagName('img') as $img){ 2611 | //if path to image is a full http url then grab image and add it to images field. 2612 | //needed for any 3rd party import (eg Wordpress Migrator) because images are being referenced back to original site, and not included in the import file. 2613 | if (strpos($img->getAttribute('src'),'//') !== false){ 2614 | //if we haven't found an images field yet 2615 | if($imagesField == ''){ 2616 | //check for standard "images" field and use that first 2617 | if($wp->images && $this->fields->images->maxFiles == 0){ 2618 | $imagesField = "images"; 2619 | } 2620 | //else find an available images field in the page template that is set to support multiple images 2621 | else{ 2622 | foreach($wp->template->fields as $field){ 2623 | if($field->type instanceof FieldtypeImage && $field->maxFiles == 0){ 2624 | $imagesField = $field->name; 2625 | break; 2626 | } 2627 | } 2628 | } 2629 | //if no images fields available in template, look in all fields and add to page template 2630 | if($imagesField == ''){ 2631 | foreach($this->fields as $field){ 2632 | if($field->type instanceof FieldtypeImage && $field->maxFiles == 0){ 2633 | $wp->template->fields->add($field->name); 2634 | $wp->template->fields->save(); 2635 | $imagesField = $field->name; 2636 | break; 2637 | } 2638 | } 2639 | } 2640 | //if no images fields available, create a new one and add to page template 2641 | if($imagesField == ''){ 2642 | $if = new Field(); 2643 | $if->type = $this->modules->get('FieldtypeImage'); 2644 | $if->name = "images"; 2645 | $if->label = "Images"; 2646 | $if->save(); // save the field 2647 | //add the new field to the page template 2648 | $wp->template->fields->add($if->name); 2649 | $wp->template->fields->save(); 2650 | $imagesField = $if->name; 2651 | } 2652 | } 2653 | //Grab image from the local path or external URL and add to images field 2654 | /*try { 2655 | $wp->$imagesField->add($img->getAttribute('src')); 2656 | if($img->getAttribute('title') != ''){ 2657 | $wp->$imagesField->last()->description = $img->getAttribute('title'); 2658 | } 2659 | elseif($img->getAttribute('alt') != ''){ 2660 | $wp->$imagesField->last()->description = $img->getAttribute('alt'); 2661 | } 2662 | 2663 | //resize image to make version to match the size originally in the RTE 2664 | //check to make sure size is different to downloaded version before resizing 2665 | if($img->getAttribute('width') && $img->getAttribute('width') != $wp->$imagesField->last()->width) { 2666 | $imgForRte = $wp->$imagesField->last()->size($img->getAttribute('width'), 0); 2667 | } 2668 | else { 2669 | $imgForRte = $wp->$imagesField->last(); 2670 | } 2671 | $img->setAttribute('src', $imgForRte->url); 2672 | } 2673 | catch(Exception $e) { 2674 | // in case remote image can't be downloaded 2675 | }*/ 2676 | 2677 | //resize image to make version to match the size originally in the RTE 2678 | //check to make sure size is different to downloaded version before resizing 2679 | /*if($img->getAttribute('width') && count($wp->$imagesField)>0) { 2680 | $imgForRte = $wp->$imagesField->last()->size($img->getAttribute('width'), 0); 2681 | } 2682 | else { 2683 | $imgForRte = $wp->$imagesField->last(); 2684 | }*/ 2685 | 2686 | //determine format for width/height from 3rd party migrated site from settings info ($this->thumb_suffix) 2687 | $thumbSuffix = str_replace("{width}", $img->getAttribute('width'), $this->thumb_suffix); 2688 | $thumbSuffix = str_replace("{height}", $img->getAttribute('height'), $thumbSuffix); 2689 | $fullsizeBasename = str_replace($thumbSuffix, "", pathinfo($img->getAttribute('src'), PATHINFO_BASENAME)); 2690 | $fullsizeImage = $wp->$imagesField->get("name=".$this->sanitizer->pageName($fullsizeBasename, true)); 2691 | $embeddedImage = $wp->$imagesField->get("name=".$this->sanitizer->pageName(pathinfo($img->getAttribute('src'), PATHINFO_BASENAME), true)); 2692 | //create resized version to match the width/height attributes specifed in the RTE img tag 2693 | if($fullsizeImage && $img->getAttribute('width') && $img->getAttribute('height')) { 2694 | $imgForRte = $fullsizeImage->size($img->getAttribute('width'), $img->getAttribute('height')); 2695 | //change RTE img src attribute to match new resized image in PW assets/files 2696 | $img->setAttribute('src', $imgForRte->url); 2697 | } 2698 | // if embedded version has same name as image added to images field 2699 | elseif($embeddedImage){ 2700 | $img->setAttribute('src', $embeddedImage->url); 2701 | } 2702 | } 2703 | else{ 2704 | //these are for images from PW imports, rather than 3rd party as above 2705 | $img->setAttribute('src', $wp->filesManager()->url() . pathinfo($img->getAttribute('src'), PATHINFO_BASENAME)); 2706 | } 2707 | } 2708 | 2709 | //replace links to images to new local path - for 3rd party migrated content 2710 | foreach($dom->getElementsByTagName('a') as $link){ 2711 | //if link path is a full http url 2712 | if (strpos($link->getAttribute('href'),'//') !== false){ 2713 | $imgExts = array("gif", "jpg", "jpeg", "png", "svg"); 2714 | $url = $link->getAttribute('href'); 2715 | $urlExt = pathinfo($url, PATHINFO_EXTENSION); 2716 | if (in_array($urlExt, $imgExts)) { 2717 | /*try { 2718 | $wp->$imagesField->add($link->getAttribute('href')); 2719 | } 2720 | catch(Exception $e) { 2721 | // in case remote image can't be downloaded 2722 | }*/ 2723 | // check if linked version of image exists - it may not if images field wasn't included in import, hence we don't want to change the links from the original 2724 | if(file_exists($wp->filesManager()->path() . $this->sanitizer->pageName(pathinfo($link->getAttribute('href'), PATHINFO_BASENAME),true))) $link->setAttribute('href', $wp->filesManager()->url() . $this->sanitizer->pageName(pathinfo($link->getAttribute('href'), PATHINFO_BASENAME),true)); 2725 | } 2726 | } 2727 | } 2728 | 2729 | return preg_replace('/^/', '', str_replace( array('', '', '', ''), array('', '', '', ''), $dom->saveHTML())); 2730 | } 2731 | 2732 | 2733 | /** 2734 | * Returns field with absatracted links converted back to normal urls (for export process) 2735 | * 2736 | */ 2737 | public function abstractedLinkDecoder($value) { 2738 | 2739 | if(!$this->modules->get("PageLinkAbstractor")) return $value; 2740 | if(!is_string($value)) return $value; 2741 | $changes = 0; 2742 | $errors = array(); 2743 | 2744 | if(strpos($value, '{~page_') !== false) { 2745 | if(preg_match_all('/\{~page_(\d+)_url\}/', $value, $matches)) { 2746 | foreach($matches[1] as $key => $id) { 2747 | $p = $this->pages->get((int) $id); 2748 | if(!$p->id) { 2749 | // notify editor that they have an invalid link 2750 | $errors[] = "Links to page ID $id that does not exist"; 2751 | continue; 2752 | } 2753 | if($p->isTrash()) { 2754 | // notify editor tthat they are linking to a page in the trash 2755 | $errors[] = "Links to page ID $id that is in the trash"; 2756 | continue; 2757 | } 2758 | $value = str_replace($matches[0][$key], $p->url, $value); 2759 | $changes++; 2760 | } 2761 | } 2762 | } 2763 | 2764 | if(strpos($value, '{~root_url}') !== false) { 2765 | $value = str_replace('{~root_url}', $this->config->urls->root, $value); 2766 | $changes++; 2767 | } 2768 | 2769 | if(count($errors)) { 2770 | $wp = $arguments[0]; 2771 | if($wp->editable()) { 2772 | foreach($errors as $error) $this->error("Page {$wp->path} $error"); 2773 | } 2774 | } 2775 | else{ 2776 | return $value; 2777 | } 2778 | 2779 | } 2780 | 2781 | 2782 | 2783 | /** 2784 | * Create zip archive of attached files for migration 2785 | * 2786 | */ 2787 | function create_zip($files = array(), $destination = '', $filetype, $overwrite = false) { 2788 | 2789 | //if the zip file already exists and overwrite is false, return false 2790 | //if(file_exists($destination) && !$overwrite) return false; 2791 | $valid_files = array(); 2792 | if(is_array($files)) { 2793 | foreach($files as $file) { 2794 | //removed the check for now due to @NooseLadder's windows/xampp path issues with file_exists 2795 | //if(file_exists($file['currentpath']) || file_exists($file[0])) $valid_files[] = $file; 2796 | $valid_files[] = $file; 2797 | } 2798 | } 2799 | if(count($valid_files)) { 2800 | //create the archive 2801 | $zip = new ZipArchive(); 2802 | if($zip->open($destination, $overwrite ? ZIPARCHIVE::OVERWRITE : ZIPARCHIVE::CREATE) !== true) return false; 2803 | //add the files 2804 | if($filetype == 'files'){ 2805 | foreach($valid_files as $path => $file) { 2806 | $zip->addFile($file['currentpath'],$file['newpath']); 2807 | } 2808 | } 2809 | elseif($filetype == 'json'){ 2810 | foreach($valid_files as $file) { 2811 | $zip->addFile($file, pathinfo($file, PATHINFO_BASENAME)); 2812 | } 2813 | } 2814 | //echo 'The zip archive contains ',$zip->numFiles,' files with a status of ',$zip->status; 2815 | $zip->close(); 2816 | 2817 | //check to make sure the file exists 2818 | return file_exists($destination); 2819 | } 2820 | else{ 2821 | return false; 2822 | } 2823 | } 2824 | 2825 | 2826 | /** 2827 | * Function to recursively delete an entire folder 2828 | * 2829 | */ 2830 | public function recursiveDelete($dirPath, $deleteParent = true){ 2831 | foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dirPath, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST) as $path) { 2832 | $path->isFile() ? unlink($path->getPathname()) : rmdir($path->getPathname()); 2833 | } 2834 | if($deleteParent) rmdir($dirPath); 2835 | } 2836 | 2837 | public function fancy_implode($arr){ 2838 | array_push($arr, implode(' and ', array_splice($arr, -2))); 2839 | return implode(', ', $arr); 2840 | } 2841 | 2842 | 2843 | public function is_dir_empty($dir) { 2844 | if (!is_readable($dir)) return NULL; 2845 | return (count(scandir($dir)) == 2); 2846 | } 2847 | 2848 | 2849 | public function downloadConfirm($name, $update = false) { 2850 | 2851 | if($this->session->download_modules != '1') return false; 2852 | 2853 | $name = $this->wire('sanitizer')->name($name); 2854 | $info = self::getModuleInfo(); 2855 | /*$this->wire('processHeadline', $this->labels['download_install']); 2856 | $this->wire('breadcrumbs')->add(new Breadcrumb('./', $info['title'])); 2857 | if($update) $this->wire('breadcrumbs')->add(new Breadcrumb("./?edit=$name", $name));*/ 2858 | 2859 | $redirectURL = $update ? "./edit?name=$name" : "./"; 2860 | $className = $name; 2861 | $url = trim($this->wire('config')->moduleServiceURL, '/') . "/$className/?apikey=" . $this->wire('sanitizer')->name($this->wire('config')->moduleServiceKey); 2862 | //echo $url; 2863 | $http = new WireHttp(); 2864 | $data = $http->get($url); 2865 | //print_r($data);exit; 2866 | if(empty($data)) { 2867 | $this->error($this->_('Error retrieving data from web service URL') . ' - ' . $http->getError()); 2868 | return $this->session->redirect($redirectURL); 2869 | } 2870 | $data = json_decode($data, true); 2871 | if(empty($data)) { 2872 | $this->error($this->_('Error decoding JSON from web service')); 2873 | return $this->session->redirect($redirectURL); 2874 | } 2875 | if($data['status'] == 'success') { 2876 | //$this->error($this->_('Error reported by web service:') . ' ' . wire('sanitizer')->entities($data['error'])); 2877 | //return $this->session->redirect($redirectURL); 2878 | 2879 | $installed = $this->modules->isInstalled($className) ? $this->modules->getModuleInfoVerbose($className) : null; 2880 | $installedVersion = $this->modules->formatVersion($installed['version']); 2881 | if(version_compare($installedVersion, $data['module_version']) < 0) { 2882 | 2883 | $destinationDir = $this->wire('config')->paths->siteModules . $className . '/'; 2884 | require_once(wire('config')->paths->modules . 'Process/ProcessModule/ProcessModuleInstall.php'); 2885 | $install = new ProcessModuleInstall(); 2886 | 2887 | $completedDir = $install->downloadModule($data['download_url'], $destinationDir); 2888 | if($completedDir) { 2889 | return true; 2890 | } 2891 | 2892 | } 2893 | 2894 | } 2895 | 2896 | /*$installable = true; 2897 | foreach($data['categories'] as $category) { 2898 | if(!in_array($category['name'], $this->uninstallableCategories)) continue; 2899 | $this->error(sprintf($this->_('Sorry modules of type "%s" are not installable from the admin.'), $category['title'])); 2900 | $installable = false; 2901 | } 2902 | if(!$installable) $this->session->redirect($redirectURL);*/ 2903 | 2904 | } 2905 | 2906 | 2907 | 2908 | /** 2909 | * Return an InputfieldsWrapper of Inputfields used to configure the class 2910 | * 2911 | * @param array $data Array of config values indexed by field name 2912 | * @return InputfieldsWrapper 2913 | * 2914 | */ 2915 | public static function getModuleConfigInputfields(array $data) { 2916 | 2917 | $data = array_merge(self::getDefaultData(), $data); 2918 | 2919 | $wrapper = new InputFieldWrapper(); 2920 | 2921 | $f = wire('modules')->get("InputfieldTextarea"); 2922 | $f->attr('id+name', 'ignoredSubFolders'); 2923 | $f->label = __("Paths of parent directories to exclude from migration"); 2924 | $f->description = __("Make paths relative to templates directory. One per line."); 2925 | $f->value = $data['ignoredSubFolders']; 2926 | $wrapper->add($f); 2927 | 2928 | return $wrapper; 2929 | 2930 | } 2931 | 2932 | 2933 | 2934 | /** 2935 | * Install the module and create the page where it lives 2936 | * 2937 | */ 2938 | public function ___install() { 2939 | 2940 | if(ProcessWire::versionMajor == 2 && ProcessWire::versionMinor < 4) { 2941 | throw new WireException("This module requires ProcessWire 2.4 or newer"); 2942 | } 2943 | 2944 | $wp = $this->getInstalledPage(); 2945 | $this->message("Installed to {$wp->path}"); 2946 | if($wp->parent->name == 'setup') $this->message("Click to your 'Setup' page to start using Migrator"); 2947 | } 2948 | 2949 | /** 2950 | * Return the page that this Process is installed on 2951 | * 2952 | */ 2953 | protected function getInstalledPage() { 2954 | 2955 | $admin = $this->pages->get($this->config->adminRootPageID); 2956 | $parent = $admin->child("name=setup"); 2957 | if(!$parent->id) $parent = $admin; 2958 | $wp = $parent->child("name=" . self::adminPageName); 2959 | 2960 | if(!$wp->id) { 2961 | $wp = new Page(); 2962 | $wp->parent = $parent; 2963 | $wp->template = $this->templates->get('admin'); 2964 | $wp->name = self::adminPageName; 2965 | $wp->title = "Migrator"; 2966 | $wp->process = $this; 2967 | $wp->sort = $parent->numChildren; 2968 | $wp->save(); 2969 | } 2970 | 2971 | return $wp; 2972 | } 2973 | 2974 | /** 2975 | * Uninstall the module 2976 | * 2977 | */ 2978 | public function ___uninstall() { 2979 | $wp = $this->getInstalledPage(); 2980 | if($wp->id) { 2981 | $this->message("Removed {$wp->path}"); 2982 | $this->pages->delete($wp); 2983 | } 2984 | } 2985 | 2986 | } 2987 | --------------------------------------------------------------------------------