├── 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.,
Copy this text and import it into your new site using the paste option.
'; 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 .= "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 .= "