diff --git a/wire/core/Fieldtype.php b/wire/core/Fieldtype.php index a1696f85..e5d66868 100644 --- a/wire/core/Fieldtype.php +++ b/wire/core/Fieldtype.php @@ -522,6 +522,7 @@ abstract class Fieldtype extends WireData implements Module { * @param Field $field * @param string|int|float|array|object $value * @return string|int|float|array + * @see Fieldtype::wakeupValue() * */ public function ___sleepValue(Page $page, Field $field, $value) { @@ -534,12 +535,15 @@ abstract class Fieldtype extends WireData implements Module { * * This is intended for importing from PW-driven web services. If not overridden, it does * the same thing as the `Fieldtype::wakeupValue()` method. + * + * #pw-internal * * @param Page $page * @param Field $field * @param string|int|float|array|null $value * @param array $options Additional options if needed/applicable * @return string|int|array|object $value + * @see Fieldtype::exportValue() * */ public function ___importValue(Page $page, Field $field, $value, array $options = array()) { @@ -548,6 +552,22 @@ abstract class Fieldtype extends WireData implements Module { return $value; } + /** + * Get associative array of options and info (name => value) that Fieldtype supports for importValue + * + * - `test` (bool): indicates Fieldtype supports testing import before committing. + * + * #pw-internal + * + * @param array Field $field + * @return array + * + */ + public function getImportValueOptions(Field $field) { + if($field) {} // ignore + return array(); + } + /** * Given a value, return an portable version of it as either a string, int, float or array * diff --git a/wire/core/PagesExportImport.php b/wire/core/PagesExportImport.php index 8256b341..3769dde0 100644 --- a/wire/core/PagesExportImport.php +++ b/wire/core/PagesExportImport.php @@ -25,6 +25,87 @@ class PagesExportImport extends Wire { + /** + * Get the path where ZIP exports are stored + * + * @param string $subdir Specify a subdirectory name if you want it to create it. + * If it exists, it will create a numbered version of the subdir to ensure it is unique. + * @return string + * + */ + public function getExportPath($subdir = '') { + + /** @var WireFileTools $files */ + $files = $this->wire('files'); + $path = $this->wire('config')->paths->assets . 'backups/' . $this->className() . '/'; + + $readmeText = "When this file is present, files and directories in here are auto-deleted after a short period of time."; + $readmeFile = $this->className() . '.txt'; + $readmeFiles = array(); + + if(!is_dir($path)) { + $files->mkdir($path, true); + $readmeFiles[] = $path . $readmeFile; + } + + if($subdir) { + $n = 0; + do { + $_path = $path . $subdir . ($n ? "-$n" : '') . '/'; + } while(++$n && is_dir($_path)); + $path = $_path; + $files->mkdir($path, true); + $readmeFiles[] = $path . $readmeFile; + } + + foreach($readmeFiles as $file) { + file_put_contents($file, $readmeText); + $files->chmod($readmeFile); + } + + return $path; + } + + /** + * Remove files and directories in /site/assets/backups/PagesExportImport/ that are older than $maxAge + * + * @param int $maxAge Maximum age in seconds + * @return int Number of files/dirs removed + * + */ + public function cleanupFiles($maxAge = 3600) { + + /** @var WireFileTools $files */ + $files = $this->wire('files'); + $path = $this->getExportPath(); + $qty = 0; + + foreach(new \DirectoryIterator($path) as $file) { + + if($file->isDot()) continue; + if($file->getBasename() == $this->className() . '.txt') continue; // we want this file to stay + if($file->getMTime() >= (time() - $maxAge)) continue; // not expired + + $pathname = $file->getPathname(); + + if($file->isDir()) { + $testFile = rtrim($pathname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $this->className() . '.txt'; + if(!is_file($testFile)) continue; + if($files->rmdir($pathname, true)) { + $this->message($this->_('Removed old directory') . " - $pathname", Notice::debug); + $qty++; + } + } else { + if(unlink($pathname)) { + $this->message($this->_('Removed old file') . " - $pathname", Notice::debug); + $qty++; + } + } + } + + return $qty; + } + /** * Export given PageArray to a ZIP file * @@ -34,22 +115,46 @@ class PagesExportImport extends Wire { * */ public function exportZIP(PageArray $items, array $options = array()) { - $tempDir = new WireTempDir($this); - $this->wire($tempDir); - $tempDir->setRemove(false); - $path = $tempDir->get(); - $jsonFile = $path . "pages.json"; - $jsonData = $this->exportJSON($items, $options); - file_put_contents($jsonFile, $jsonData); + /** @var WireFileTools $files */ $files = $this->wire('files'); - $zipFileItems = array($jsonFile); - $zipFileName = $path . 'pages.zip'; - $zipFileInfo = $files->zip($zipFileName, $zipFileItems); - foreach($zipFileItems as $file) { - unlink($file); + + $options['exportTarget'] = 'zip'; + $zipPath = $this->getExportPath(); + if(!is_dir($zipPath)) $files->mkdir($zipPath, true); + + $tempDir = new WireTempDir($this); + $this->wire($tempDir); + $tmpPath = $tempDir->get(); + $jsonFile = $tmpPath . "pages.json"; + $zipItems = array($jsonFile); + $data = $this->pagesToArray($items, $options); + + // determine other files to add to ZIP + foreach($data['pages'] as $key => $item) { + if(!isset($item['_filesPath'])) continue; + $zipItems[] = $item['_filesPath']; + unset($data['pages'][$key]['_filesPath']); } - return $zipFileName; + + // write out the pages.json file + file_put_contents($jsonFile, wireEncodeJSON($data, true, true)); + + $n = 0; + do { + $zipName = $zipPath . 'pages' . ($n ? "-$n" : '') . '.zip'; + } while(++$n && file_exists($zipName)); + + // @todo report errors from zipInfo + $zipInfo = $files->zip($zipName, $zipItems, array( + 'maxDepth' => 1, + 'allowHidden' => false, + 'allowEmptyDirs' => false + )); + + unlink($jsonFile); + + return $zipName; } /** @@ -61,17 +166,23 @@ class PagesExportImport extends Wire { * */ public function importZIP($filename, array $options = array()) { + $tempDir = new WireTempDir($this); $this->wire($tempDir); $path = $tempDir->get(); + $options['filesPath'] = $path; + $zipFileItems = $this->wire('files')->unzip($filename, $path); - if(empty($zipFileItems)) { - $pageArray = false; - } else { - $jsonFile = $path . "pages.json"; - $jsonData = file_get_contents($jsonFile); - $pageArray = $this->importJSON($jsonData, $options); - } + + if(empty($zipFileItems)) return false; + + $jsonFile = $path . "pages.json"; + $jsonData = file_get_contents($jsonFile); + $data = json_decode($jsonData, true); + if($data === false) return false; + + $pageArray = $this->arrayToPages($data, $options); + return $pageArray; } @@ -84,6 +195,10 @@ class PagesExportImport extends Wire { * */ public function exportJSON(PageArray $items, array $options = array()) { + $defaults = array( + 'exportTarget' => 'json' + ); + $options = array_merge($defaults, $options); $data = $this->pagesToArray($items, $options); $data = wireEncodeJSON($data, true, true); return $data; @@ -126,10 +241,14 @@ class PagesExportImport extends Wire { $a = array( 'type' => 'ProcessWire:PageArray', + 'created' => date('Y-m-d H:i:s'), 'version' => $this->wire('config')->version, - 'pagination' => array(), + 'user' => $this->wire('user')->name, + 'host' => $this->wire('config')->httpHost, 'pages' => array(), 'fields' => array(), + 'timer' => Debug::timer(), + // 'pagination' => array(), ); if($items->getLimit()) { @@ -190,6 +309,10 @@ class PagesExportImport extends Wire { } } $a['fields'][$fieldName]['blankValue'] = $blankValue; + foreach($field->type->getImportValueOptions($field) as $k => $v) { + if(isset($a['fields'][$fieldName][$k])) continue; + $a['fields'][$fieldName][$k] = $v; + } } } @@ -205,6 +328,7 @@ class PagesExportImport extends Wire { // sort by path to ensure parents are created before their children ksort($a['pages']); $a['pages'] = array_values($a['pages']); + $a['timer'] = Debug::timer($a['timer']); if($options['verbose']) $a['templates'] = $templates; @@ -223,12 +347,18 @@ class PagesExportImport extends Wire { */ protected function pageToArray(Page $page, array $options) { + $defaults = array( + 'exportTarget' => '', + ); + $options = array_merge($defaults, $options); + $of = $page->of(); $page->of(false); /** @var Languages $languages */ $languages = $this->wire('languages'); if($languages) $languages->setDefault(); + $numFiles = 0; // standard page settings $settings = array( @@ -290,16 +420,20 @@ class PagesExportImport extends Wire { if(!empty($options['fieldNames']) && !in_array($field->name, $options['fieldNames'])) continue; $info = $this->getFieldInfo($field); - if(!$info['exportable']) { - // $a['warnings'][$field->name] = $info['reason']; - // $this->warning("Field '$field->name' - $info[reason]"); - continue; - } + if(!$info['exportable']) continue; $value = $page->getUnformatted($field->name); $exportValue = $field->type->exportValue($page, $field, $value, $exportValueOptions); - // $this->message($exportValue); + $a['data'][$field->name] = $exportValue; + + if($field->type instanceof FieldtypeFile && $value) { + $numFiles += count($value); + } + } + + if($numFiles && $options['exportTarget'] == 'zip') { + $a['_filesPath'] = $page->filesManager()->path(); } if($of) $page->of(true); @@ -412,6 +546,7 @@ class PagesExportImport extends Wire { 'replaceFields' => array(), // array of import-data field name to replacement page field name 'replaceTemplates' => array(), // array of import-data template name to replacement page template name 'replaceParents' => array(), // array of import-data parent path to replacement parent path + 'filesPath' => '', // path where file field directories are located when importing from zip (internal use) 'commit' => true, // commit the import? If false, changes aren't saved (dry run). 'debug' => false, ); @@ -747,6 +882,8 @@ class PagesExportImport extends Wire { } } + $fieldtypeSupportsOptions = $field->type->getImportValueOptions($field); + $o = array( 'system' => true, 'caller' => $this, @@ -755,8 +892,7 @@ class PagesExportImport extends Wire { ); // fake-commit for more verbose testing of certain fieldtypes - $fakeCommitTypes = array('FieldtypePage', 'FieldtypeRepeater', 'FieldtypeComments'); - $fakeCommit = $options['commit'] || wireInstanceOf($field->type, $fakeCommitTypes); + $fakeCommit = $options['commit'] || !empty($fieldtypeSupportsOptions['test']); if($page->get('_importType') == 'create' && !$options['commit'] && !$fakeCommit) { // test import on a new page, so value will always be used @@ -828,13 +964,14 @@ class PagesExportImport extends Wire { $filesAdded = array(); $filesUpdated = array(); $filesRemoved = array(); - $filesDownloaded = array(); + $variationsAdded = array(); $maxFiles = (int) $field->get('maxFiles'); $languages = $this->wire('languages'); $filesPath = $pagefiles->path(); - /** @var WireHttp $http */ - $http = $this->wire(new WireHttp()); + /** @var null|WireHttp $http */ + $http = null; + $pageID = $page->get('_importOriginalID'); foreach($data as $fileName => $fileInfo) { @@ -847,7 +984,13 @@ class PagesExportImport extends Wire { $isNew = true; try { if($options['commit']) { - $pagefiles->add($fileInfo['url']); + if(empty($options['filesPath'])) { + // importing from ZIP where files are located under filesPath option + $pagefiles->add($fileInfo['url']); + } else { + // importing from URL + $pagefiles->add("$options[filesPath]$pageID/$fileName"); + } $pagefile = $pagefiles->last(); if(!$pagefile) throw new WireException("Unable to add file $fileInfo[url]"); if($maxFiles === 1 && $pagefiles->count() > 1) { @@ -870,6 +1013,7 @@ class PagesExportImport extends Wire { // description, tags, etc. foreach($fileInfo as $key => $value) { if($key == 'url') continue; + if($key == 'size') continue; if($key == 'variations') { $variations = $value; continue; @@ -903,22 +1047,47 @@ class PagesExportImport extends Wire { } } } - - if(count($variations)) { - foreach($variations as $name => $url) { - $targetFile = $filesPath . $name; - if(!file_exists($targetFile)) { - try { - if($options['commit']) $http->download($url, $targetFile); - $filesDownloaded[] = $name; - } catch(\Exception $e) { - $page->warning("Error downloading file $url - " . $e->getMessage()); - } + + // image variations + foreach($variations as $name => $url) { + + $targetFile = $filesPath . $name; + $sourceFile = empty($options['filesPath']) ? '' : "$options[filesPath]$pageID/$name"; + $targetExists = file_exists($targetFile); + $sourceExists = $sourceFile ? file_exists($sourceFile) : false; + + if($sourceExists && $targetExists) { + // skip because they are likely the same + if(filesize($sourceFile) == filesize($targetFile)) continue; + } else if($targetExists) { + // target already exists so skip it (since we don't have a way to check size) + continue; + } + + if(!$options['commit']) { + $variationsAdded[] = $name; + continue; + } + + if($sourceExists) { + // copy variation from options[filesPath] + if($this->wire('files')->copy($sourceFile, $targetFile)) { + $variationsAdded[] = $name; + } else { + $page->warning("Unable to copy file (image variation): $sourceFile"); + } + } else { + // download variation via http + try { + if(is_null($http)) $http = $this->wire(new WireHttp()); + $http->download($url, $targetFile); + $variationsAdded[] = $name; + } catch(\Exception $e) { + $page->warning("Error downloading file (image variation): $url - " . $e->getMessage()); } } } } - // determine removed files foreach($pagefiles as $pagefile) { @@ -926,12 +1095,13 @@ class PagesExportImport extends Wire { $filesRemoved[] = $pagefile->name; if($options['commit']) $pagefiles->remove($pagefile); } - + + // summarize all of the above $numAdded = count($filesAdded); $numUpdated = count($filesUpdated); $numRemoved = count($filesRemoved); - $numDownloaded = count($filesDownloaded); - $numTotal = $numAdded + $numUpdated + $numRemoved; // intentionally excludes numDownloaded + $numVariations = count($variationsAdded); + $numTotal = $numAdded + $numUpdated + $numRemoved; // intentionally excludes numVariations if($numTotal > 0) { $pagefiles->trackChange('value'); @@ -956,11 +1126,14 @@ class PagesExportImport extends Wire { ); } - if($numDownloaded) { + if($numVariations) { + $addedType = $http === null ? 'ZIP copy' : 'HTTP download'; $page->trackChange($field->name); $page->message("$field->name (variation): " . - sprintf($this->_n('Downloaded %d file', 'Downloaded %d files', $numDownloaded), $numDownloaded) . ": " . - implode(', ', $filesDownloaded) + sprintf( + $this->_n('Added %d file via %s', 'Added %d files via %s', $numVariations), + $numVariations, $addedType + ) . ": " . implode(', ', $variationsAdded) ); } } diff --git a/wire/core/ProcessWire.php b/wire/core/ProcessWire.php index 504eff56..b4198004 100644 --- a/wire/core/ProcessWire.php +++ b/wire/core/ProcessWire.php @@ -45,7 +45,7 @@ class ProcessWire extends Wire { * Reversion revision number * */ - const versionRevision = 71; + const versionRevision = 72; /** * Version suffix string (when applicable) diff --git a/wire/core/WireFileTools.php b/wire/core/WireFileTools.php index ee8bf796..bc9906d8 100644 --- a/wire/core/WireFileTools.php +++ b/wire/core/WireFileTools.php @@ -433,6 +433,7 @@ class WireFileTools extends Wire { * Note that if you actually specify a hidden file in your $files argument, then that overrides this. * - `allowEmptyDirs` (boolean): allow empty directories in the ZIP file? (default=true) * - `overwrite` (boolean): Replaces ZIP file if already present (rather than adding to it) (default=false) + * - `maxDepth` (int): Max dir depth 0 for no limit (default=0). Specify 1 to stay only in dirs listed in $files. * - `exclude` (array): Files or directories to exclude * - `dir` (string): Directory name to prepend to added files in the ZIP * @return array Returns associative array of: @@ -443,11 +444,14 @@ class WireFileTools extends Wire { * */ public function zip($zipfile, $files, array $options = array()) { + + static $depth = 0; $defaults = array( 'allowHidden' => false, 'allowEmptyDirs' => true, 'overwrite' => false, + 'maxDepth' => 0, 'exclude' => array(), // files or dirs to exclude 'dir' => '', 'zip' => null, // internal use: holds ZipArchive instance for recursive use @@ -457,7 +461,7 @@ class WireFileTools extends Wire { 'files' => array(), 'errors' => array(), ); - + if(!empty($options['zip']) && !empty($options['dir']) && $options['zip'] instanceof \ZipArchive) { // internal recursive call $recursive = true; @@ -490,23 +494,35 @@ class WireFileTools extends Wire { if(!$options['allowHidden']) continue; if(is_array($options['allowHidden']) && !in_array($basename, $options['allowHidden'])) continue; } - if(count($options['exclude']) && (in_array($name, $options['exclude']) || in_array("$name/", $options['exclude']))) continue; + if(count($options['exclude'])) { + if(in_array($name, $options['exclude']) || in_array("$name/", $options['exclude'])) continue; + } if(is_dir($file)) { + if($options['maxDepth'] > 0 && $depth >= $options['maxDepth']) continue; $_files = array(); - foreach(new \DirectoryIterator($file) as $f) if(!$f->isDot()) $_files[] = $f->getPathname(); + foreach(new \DirectoryIterator($file) as $f) { + if($f->isDot()) continue; + if($options['maxDepth'] > 0 && $f->isDir() && ($depth+1) >= $options['maxDepth']) continue; + $_files[] = $f->getPathname(); + } if(count($_files)) { $zip->addEmptyDir($name); $options['dir'] = "$name/"; $options['zip'] = $zip; + $depth++; $_return = $this->zip($zipfile, $_files, $options); + $depth--; foreach($_return['files'] as $s) $return['files'][] = $s; foreach($_return['errors'] as $s) $return['errors'][] = $s; } else if($options['allowEmptyDirs']) { $zip->addEmptyDir($name); } } else if(file_exists($file)) { - if($zip->addFile($file, $name)) $return['files'][] = $name; - else $return['errors'][] = $name; + if($zip->addFile($file, $name)) { + $return['files'][] = $name; + } else { + $return['errors'][] = $name; + } } } diff --git a/wire/modules/Fieldtype/FieldtypeComments/FieldtypeComments.module b/wire/modules/Fieldtype/FieldtypeComments/FieldtypeComments.module index 32828a91..63aefdb6 100644 --- a/wire/modules/Fieldtype/FieldtypeComments/FieldtypeComments.module +++ b/wire/modules/Fieldtype/FieldtypeComments/FieldtypeComments.module @@ -1586,8 +1586,7 @@ class FieldtypeComments extends FieldtypeMulti { * */ public function ___exportValue(Page $page, Field $field, $value, array $options = array()) { - $exportValue = array_values($this->exportComments($value)); - return $exportValue; + return array_values($this->exportComments($value)); } /** @@ -1693,5 +1692,20 @@ class FieldtypeComments extends FieldtypeMulti { } + /** + * Get associative array of options (name => default) that Fieldtype supports for importValue + * + * #pw-internal + * + * @param Field $field + * @return array + * + */ + public function getImportValueOptions(Field $field) { + $options = parent::getImportValueOptions($field); + $options['test'] = true; + return $options; + } + } diff --git a/wire/modules/Fieldtype/FieldtypeFile.module b/wire/modules/Fieldtype/FieldtypeFile.module index 04cb305e..e4e25409 100644 --- a/wire/modules/Fieldtype/FieldtypeFile.module +++ b/wire/modules/Fieldtype/FieldtypeFile.module @@ -265,14 +265,17 @@ class FieldtypeFile extends FieldtypeMulti { foreach($value as $k => $v) { /** @var Pagefile $pagefile */ $pagefile = $pagefiles->get($v['data']); - $a = array('url' => $pagefile->httpUrl()); + + $a = array( + 'url' => $pagefile->httpUrl(), + 'size' => $pagefile->filesize(), + ); if(!empty($options['system'])) { unset($v['created'], $v['modified']); $exportKey = $v['data']; } else { $a['name'] = $v['data']; - $a['filesize'] = $pagefile->filesize(); $exportKey = $k; } @@ -281,7 +284,7 @@ class FieldtypeFile extends FieldtypeMulti { if($options['FieldtypeFile']['noJSON']) { // export version 2 for exported description uses array value for multi-language, rather than JSON string if(!isset($v['description'])) $v['description'] = ''; - $v['description'] = $this->exportDescription($v['description'], $options); + $v['description'] = $this->exportDescription($v['description']); } $exportValue[$exportKey] = array_merge($a, $v); diff --git a/wire/modules/Fieldtype/FieldtypePage.module b/wire/modules/Fieldtype/FieldtypePage.module index 67829960..8db24241 100644 --- a/wire/modules/Fieldtype/FieldtypePage.module +++ b/wire/modules/Fieldtype/FieldtypePage.module @@ -334,6 +334,8 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule } else if(is_string($item)) { // system option $path = $item; + } else { + continue; } $p = $this->wire('pages')->get($path); if(!$p->id) { @@ -349,6 +351,20 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule return $pageArray; } + /** + * Get associative array of options (name => default) that Fieldtype supports for importValue + * + * #pw-internal + * + * @param Field $field + * @return array + * + */ + public function getImportValueOptions(Field $field) { + $options = parent::getImportValueOptions($field); + $options['test'] = true; + return $options; + } /** * Format the given value for output. diff --git a/wire/modules/Fieldtype/FieldtypeRepeater/FieldtypeRepeater.module b/wire/modules/Fieldtype/FieldtypeRepeater/FieldtypeRepeater.module index 1a271cac..a4bc96df 100644 --- a/wire/modules/Fieldtype/FieldtypeRepeater/FieldtypeRepeater.module +++ b/wire/modules/Fieldtype/FieldtypeRepeater/FieldtypeRepeater.module @@ -17,6 +17,7 @@ * * @property int $repeatersRootPageID * @method saveConfigInputfields(Field $field, Template $template, Page $parent) + * @method readyPageSaved(Page $readyPage, Page $ownerPage, Field $field) Hook called when ready page is saved * * Page status notes for repeater items: * - Unpublished & Hidden: Ready page, not yet used. Appears in unformatted repeater PageArray but user has not saved it. @@ -614,7 +615,7 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule { $parent_id = null; $field_parent_id = $field->get('parent_id'); $template_id = $field->get('template_id'); - $outputFormatting = $page->outputFormatting(); + // $outputFormatting = $page->outputFormatting(); // if it's already in the target format, leave it if($value instanceof PageArray) return $value; @@ -885,6 +886,20 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule { return $pageArray; } + /** + * Get associative array of options (name => default) that Fieldtype supports for importValue + * + * #pw-internal + * + * @param Field $field + * @return array + * + */ + public function getImportValueOptions(Field $field) { + $options = parent::getImportValueOptions($field); + $options['test'] = true; + return $options; + } /** * Get information used by selectors for querying this field @@ -982,7 +997,6 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule { return $parent; } - /** * Return the repeater template used by Field, i.e. repeater_name * @@ -1092,6 +1106,7 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule { */ protected function sanitizeValueString(Page $page, Field $field, $value) { + if($page) {} // ignore $result = false; if(ctype_digit("$value")) { @@ -1577,6 +1592,7 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule { * */ protected function ___saveConfigInputfields(Field $field, Template $template, Page $parent) { + if($parent) {} // ignore require_once(__DIR__ . '/config.php'); $helper = new FieldtypeRepeaterConfigHelper($field); $helper->saveConfigInputfields($template); @@ -1590,6 +1606,7 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule { * */ public function getModuleConfigInputfields(array $data) { + if($data) {} // ignore return $this->wire(new InputfieldWrapper()); } diff --git a/wire/modules/Process/ProcessPagesExportImport/ProcessPagesExportImport.module b/wire/modules/Process/ProcessPagesExportImport/ProcessPagesExportImport.module index 4ec92d09..88f89886 100644 --- a/wire/modules/Process/ProcessPagesExportImport/ProcessPagesExportImport.module +++ b/wire/modules/Process/ProcessPagesExportImport/ProcessPagesExportImport.module @@ -41,15 +41,6 @@ class ProcessPagesExportImport extends Process { */ protected $exportImport; - /** - * Construct - * - */ - public function __construct() { - $this->exportImport = $this->wire(new PagesExportImport()); - parent::__construct(); - } - /** * Main execution handler * @@ -63,6 +54,10 @@ class ProcessPagesExportImport extends Process { throw new WirePermissionException($this->_('Export/import is currently only available to superuser')); } + $this->exportImport = new PagesExportImport(); + $this->wire($this->exportImport); + $this->exportImport->cleanupFiles(600); + $input = $this->wire('input'); $user = $this->wire('user'); $breadcrumbLabel = $this->wire('page')->title; @@ -113,6 +108,8 @@ class ProcessPagesExportImport extends Process { $form = $modules->get('InputfieldForm'); $form->attr('id', 'ProcessPagesExportImport'); + $form->attr('method', 'post'); + $form->attr('enctype', 'multipart/form-data'); if($user->hasPermission('page-edit-export')) { if(!$tab || $tab == 'export') $form->add($this->buildExportTab()); @@ -147,19 +144,18 @@ class ProcessPagesExportImport extends Process { $f->description = $this->_('Paste in the JSON string previously exported from this tool.'); $tab->add($f); - /* $f = $modules->get('InputfieldFile'); $f->name = 'import_zip'; - $f->label = $this->_('Import from ZIP file upload'); + $f->label = $this->_('Import from ZIP file upload') . " (experimental)"; $f->extensions = 'zip'; $f->icon = 'upload'; $f->maxFiles = 1; $f->unzip = 0; $f->overwrite = false; - $f->setMaxFilesize('100g'); + $f->setMaxFilesize('10g'); $f->collapsed = Inputfield::collapsedYes; + $f->destinationPath = $this->exportImport->getExportPath(); $tab->add($f); - */ $f = $modules->get('InputfieldSubmit'); $f->attr('name', 'submit_import'); @@ -169,7 +165,7 @@ class ProcessPagesExportImport extends Process { return $tab; } - + /** * Process a submitted import and return form with summary data * @@ -184,32 +180,44 @@ class ProcessPagesExportImport extends Process { /** @var InputfieldWrapper $importTab */ $importTab = $this->buildForm('import'); - + $submitCommit = $input->post('submit_commit_import') ? true : false; $submitTest = $input->post('submit_test_import') ? true : false; + $submitZIP = !empty($_FILES['import_zip']); $fileField = null; + $filesPath = $this->wire('session')->getFor($this, 'filesPath'); + $jsonFile = ''; $a = null; - if(!empty($_POST['import_zip']) && count($_FILES) && empty($_POST['import_json'])) { + if($submitZIP) { // ZIP file import - throw new WireException('ZIP import not yet supported'); - /* - $tempDir = new WireTempDir(); - $this->wire($tempDir); - $tempDir->create($this); - $fileField = $importTab->getChildByName('import_zip'); - $fileField->destinationPath = $tempDir->get(); $importTab->processInput($input->post); - foreach($fileField->value as $pagefile) { } - $this->warning($fileField->value); - */ + $fileField = $importTab->getChildByName('import_zip'); + $zipFile = $this->exportImport->getExportPath() . $fileField->value->first()->name; + if(!$zipFile || !is_file($zipFile)) throw new WireException('No ZIP file found: ' . $zipFile); + $unzipPath = $this->exportImport->getExportPath('import-zip'); + $zipFileItems = $this->wire('files')->unzip($zipFile, $unzipPath); + unlink($zipFile); + if(empty($zipFileItems)) throw new WireException("No files found in ZIP"); + $jsonFile = $unzipPath . "pages.json"; + $this->wire('session')->setFor($this, 'filesPath', $unzipPath); - } else { + } else if(!empty($_POST['import_json'])) { // JSON import $importTab->processInput($input->post); $json = $importTab->getChildByName('import_json')->val(); if(empty($json)) throw new WireException($this->_('No import data found')); - $a = json_decode($json, true); + $a = json_decode($json, true); + $this->wire('session')->setFor($this, 'filesPath', ''); + + } else if($filesPath) { + // ZIP import commit or test + $jsonFile = $filesPath . "pages.json"; + } + + if($jsonFile) { + if(!is_file($jsonFile)) throw new WireException("No pages.json found in ZIP file"); + $a = json_decode(file_get_contents($jsonFile), true); } if(!is_array($a)) throw new WireException($this->_('Invalid import data')); @@ -298,6 +306,7 @@ class ProcessPagesExportImport extends Process { 'changeName' => in_array('name', $fieldNames), 'changeStatus' => in_array('status', $fieldNames), 'changeSort' => in_array('sort', $fieldNames), + 'filesPath' => $this->wire('session')->getFor($this, 'filesPath'), ); foreach($a['pages'] as $key => $item) { @@ -598,7 +607,6 @@ class ProcessPagesExportImport extends Process { } } - /** * Import item to a Page and return it * @@ -979,7 +987,7 @@ class ProcessPagesExportImport extends Process { $f->showIf = $showIf; $f->icon = 'download'; $f->addActionValue('json', $this->_('JSON for copy/paste (default)'), 'scissors'); - $f->addActionValue('zip', $this->_('ZIP file download (coming soon)'), 'download'); + $f->addActionValue('zip', $this->_('ZIP file download') . ' (experimental)', 'download'); $tab->add($f); return $tab; @@ -1015,7 +1023,6 @@ class ProcessPagesExportImport extends Process { $exportType = $tab->getChildByName('export_type')->val(); $exportFields = $tab->getChildByName('export_fields')->val(); $exportTo = $input->post('submit_export') === 'zip' ? 'zip' : 'json'; - if($exportTo == 'zip') throw new WireException('ZIP export not yet available'); // @todo security and access control // @todo paginate large sets @@ -1079,7 +1086,8 @@ class ProcessPagesExportImport extends Process { $f->val($json); $form->add($f); return $form->render() . "
"; - } else { + + } else if($exportTo == 'zip') { // zip file download $zipFile = $exporter->exportZIP($exportPages, $exportOptions); if($zipFile) { @@ -1093,6 +1101,8 @@ class ProcessPagesExportImport extends Process { throw new WireException('Export failed during ZIP file generation'); } } + + return ''; } /**