mirror of
https://github.com/processwire/processwire.git
synced 2025-08-12 17:54:44 +02:00
Add Repeater field support to new Page Export/Import features.
This commit is contained in:
@@ -33,6 +33,7 @@
|
|||||||
* @method mixed wakeupValue(Page $page, Field $field, $value)
|
* @method mixed wakeupValue(Page $page, Field $field, $value)
|
||||||
* @method string|int|array sleepValue(Page $page, Field $field, $value)
|
* @method string|int|array sleepValue(Page $page, Field $field, $value)
|
||||||
* @method string|float|int|array exportValue(Page $page, Field $field, $value, array $options = array())
|
* @method string|float|int|array exportValue(Page $page, Field $field, $value, array $options = array())
|
||||||
|
* @method string|float|int|array|object importValue(Page $page, Field $field, $value, array $options = array())
|
||||||
* @method bool createField(Field $field)
|
* @method bool createField(Field $field)
|
||||||
* @method array getSelectorInfo(Field $field, array $data = array())
|
* @method array getSelectorInfo(Field $field, array $data = array())
|
||||||
* @method mixed|null loadPageField(Page $page, Field $field)
|
* @method mixed|null loadPageField(Page $page, Field $field)
|
||||||
@@ -1267,7 +1268,7 @@ abstract class Fieldtype extends WireData implements Module {
|
|||||||
if($key == 'name') return $this->className();
|
if($key == 'name') return $this->className();
|
||||||
if($key == 'shortName') {
|
if($key == 'shortName') {
|
||||||
return str_replace('Fieldtype', '', $this->className());
|
return str_replace('Fieldtype', '', $this->className());
|
||||||
} else if($key == 'longName') {
|
} else if($key == 'longName' && method_exists($this, 'getModuleInfo')) {
|
||||||
$info = $this->getModuleInfo($this);
|
$info = $this->getModuleInfo($this);
|
||||||
return $info['title'];
|
return $info['title'];
|
||||||
}
|
}
|
||||||
|
@@ -159,19 +159,26 @@ class PagesExportImport extends Wire {
|
|||||||
// include information about field settings so that warnings can be generated at
|
// include information about field settings so that warnings can be generated at
|
||||||
// import time if there are applicable differences in the field settings
|
// import time if there are applicable differences in the field settings
|
||||||
foreach($exportItem['data'] as $fieldName => $value) {
|
foreach($exportItem['data'] as $fieldName => $value) {
|
||||||
|
$fieldNames = array($fieldName);
|
||||||
|
if(is_array($value) && !empty($value['type']) && $value['type'] == 'ProcessWire:PageArray') {
|
||||||
|
// nested PageArray, pull in fields from it as well
|
||||||
|
foreach(array_keys($value['fields']) as $fieldName) $fieldNames[] = $fieldName;
|
||||||
|
}
|
||||||
|
foreach($fieldNames as $fieldName) {
|
||||||
if(isset($a['fields'][$fieldName])) continue;
|
if(isset($a['fields'][$fieldName])) continue;
|
||||||
$field = $this->wire('fields')->get($fieldName);
|
$field = $this->wire('fields')->get($fieldName);
|
||||||
if(!$field || !$field->type) continue;
|
if(!$field || !$field->type) continue;
|
||||||
$moduleInfo = $this->wire('modules')->getModuleInfoVerbose($field->type);
|
$moduleInfo = $this->wire('modules')->getModuleInfoVerbose($field->type);
|
||||||
if($options['verbose']) {
|
if($options['verbose']) {
|
||||||
$fieldData = $field->getExportData();
|
$fieldData = $field->getExportData();
|
||||||
unset($fieldData['id'], $fieldData['name']);
|
unset($fieldData['name']);
|
||||||
$a['fields'][$fieldName] = $fieldData;
|
$a['fields'][$fieldName] = $fieldData;
|
||||||
} else {
|
} else {
|
||||||
$a['fields'][$fieldName] = array(
|
$a['fields'][$fieldName] = array(
|
||||||
'type' => $field->type->className(),
|
'type' => $field->type->className(),
|
||||||
'label' => $field->label,
|
'label' => $field->label,
|
||||||
'version' => $moduleInfo['versionStr']
|
'version' => $moduleInfo['versionStr'],
|
||||||
|
'id' => $field->id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$blankValue = $field->type->getBlankValue($item, $field);
|
$blankValue = $field->type->getBlankValue($item, $field);
|
||||||
@@ -184,6 +191,7 @@ class PagesExportImport extends Wire {
|
|||||||
}
|
}
|
||||||
$a['fields'][$fieldName]['blankValue'] = $blankValue;
|
$a['fields'][$fieldName]['blankValue'] = $blankValue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// include information about template settings so that warnings can be generated
|
// include information about template settings so that warnings can be generated
|
||||||
// at import time if there are applicable differences in the template settings
|
// at import time if there are applicable differences in the template settings
|
||||||
@@ -319,10 +327,15 @@ class PagesExportImport extends Wire {
|
|||||||
|
|
||||||
$defaults = array(
|
$defaults = array(
|
||||||
'count' => false, // Return count of imported pages, rather than PageArray (reduced memory requirements)
|
'count' => false, // Return count of imported pages, rather than PageArray (reduced memory requirements)
|
||||||
|
'pageArray' => null,
|
||||||
);
|
);
|
||||||
|
|
||||||
$options = array_merge($defaults, $options);
|
$options = array_merge($defaults, $options);
|
||||||
|
if(!empty($options['pageArray']) && $options['pageArray'] instanceof PageArray) {
|
||||||
|
$pageArray = $options['pageArray'];
|
||||||
|
} else {
|
||||||
$pageArray = $this->wire('pages')->newPageArray();
|
$pageArray = $this->wire('pages')->newPageArray();
|
||||||
|
}
|
||||||
$count = 0;
|
$count = 0;
|
||||||
|
|
||||||
// $a has: type (string), version (string), pagination (array), pages (array), fields (array)
|
// $a has: type (string), version (string), pagination (array), pages (array), fields (array)
|
||||||
@@ -331,16 +344,14 @@ class PagesExportImport extends Wire {
|
|||||||
|
|
||||||
// @todo generate warnings from this import info
|
// @todo generate warnings from this import info
|
||||||
$info = $this->getImportInfo($a);
|
$info = $this->getImportInfo($a);
|
||||||
|
if($info) {}
|
||||||
|
|
||||||
foreach($a['pages'] as $item) {
|
foreach($a['pages'] as $item) {
|
||||||
$page = $this->arrayToPage($item, $options);
|
$page = $this->arrayToPage($item, $options);
|
||||||
$e = $page->errors('string clear');
|
|
||||||
$w = $page->warnings('string clear');
|
|
||||||
$id = $item['settings']['id'];
|
$id = $item['settings']['id'];
|
||||||
if(strlen($e)) foreach(explode("\n", $e) as $s) $pageArray->error("Page $id: $s");
|
$this->wire('notices')->move($page, $pageArray, array('prefix' => "Page $id: "));
|
||||||
if(strlen($w)) foreach(explode("\n", $w) as $s) $pageArray->warning("Page $id: $s");
|
|
||||||
$count++;
|
|
||||||
if(!$options['count']) $pageArray->add($page);
|
if(!$options['count']) $pageArray->add($page);
|
||||||
|
$count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $options['count'] ? $count : $pageArray;
|
return $options['count'] ? $count : $pageArray;
|
||||||
@@ -390,6 +401,7 @@ class PagesExportImport extends Wire {
|
|||||||
'template' => '', // Template object, name or ID. (0=auto detect from imported page template)
|
'template' => '', // Template object, name or ID. (0=auto detect from imported page template)
|
||||||
'update' => true, // allow update of existing pages?
|
'update' => true, // allow update of existing pages?
|
||||||
'create' => true, // allow creation of new pages?
|
'create' => true, // allow creation of new pages?
|
||||||
|
'delete' => false, // allow deletion of pages? (@todo)
|
||||||
'changeTemplate' => false, // allow template to be changed on updated pages? (requires update=true)
|
'changeTemplate' => false, // allow template to be changed on updated pages? (requires update=true)
|
||||||
'changeParent' => false,
|
'changeParent' => false,
|
||||||
'changeName' => true,
|
'changeName' => true,
|
||||||
@@ -728,7 +740,8 @@ class PagesExportImport extends Wire {
|
|||||||
if($field->type instanceof FieldtypeFile) {
|
if($field->type instanceof FieldtypeFile) {
|
||||||
// file fields (cannot be accessed until page exists)
|
// file fields (cannot be accessed until page exists)
|
||||||
if($page->id) {
|
if($page->id) {
|
||||||
return $this->importFileFieldValue($page, $field, $importValue, $options);
|
$this->importFileFieldValue($page, $field, $importValue, $options);
|
||||||
|
return;
|
||||||
} else if(!empty($importValue)) {
|
} else if(!empty($importValue)) {
|
||||||
$page->trackChange($field->name);
|
$page->trackChange($field->name);
|
||||||
}
|
}
|
||||||
@@ -737,6 +750,7 @@ class PagesExportImport extends Wire {
|
|||||||
$o = array(
|
$o = array(
|
||||||
'system' => true,
|
'system' => true,
|
||||||
'caller' => $this,
|
'caller' => $this,
|
||||||
|
'commit' => $options['commit'],
|
||||||
'test' => !$options['commit']
|
'test' => !$options['commit']
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -745,6 +759,7 @@ class PagesExportImport extends Wire {
|
|||||||
if(!$options['commit']) {
|
if(!$options['commit']) {
|
||||||
// we fake-commit Page refs so that validity is tested and errors known before commit
|
// we fake-commit Page refs so that validity is tested and errors known before commit
|
||||||
if($field->type instanceof FieldtypePage) $fakeCommit = true;
|
if($field->type instanceof FieldtypePage) $fakeCommit = true;
|
||||||
|
if($field->type instanceof FieldtypeRepeater) $fakeCommit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($page->get('_importType') == 'create' && !$options['commit'] && !$fakeCommit) {
|
if($page->get('_importType') == 'create' && !$options['commit'] && !$fakeCommit) {
|
||||||
@@ -768,14 +783,8 @@ class PagesExportImport extends Wire {
|
|||||||
$pageValue = $field->type->importValue($page, $field, $importValue, $o);
|
$pageValue = $field->type->importValue($page, $field, $importValue, $o);
|
||||||
if($pageValue !== null) $page->set($field->name, $pageValue);
|
if($pageValue !== null) $page->set($field->name, $pageValue);
|
||||||
if(is_object($pageValue) && $pageValue instanceof Wire) {
|
if(is_object($pageValue) && $pageValue instanceof Wire) {
|
||||||
// copy notices from the pageValue to the page
|
// movie notices from the pageValue to the page
|
||||||
foreach(array('errors', 'warnings', 'messages') as $noticeType) {
|
$this->wire('notices')->move($pageValue, $page);
|
||||||
foreach($pageValue->$noticeType('clear') as $notice) {
|
|
||||||
$method = rtrim($noticeType, 's');
|
|
||||||
$page->$method($notice->text);
|
|
||||||
$this->warning($notice->text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// test import on existing page, avoids actually setting value to the page
|
// test import on existing page, avoids actually setting value to the page
|
||||||
@@ -1135,6 +1144,9 @@ class PagesExportImport extends Wire {
|
|||||||
if($fieldtype instanceof FieldtypeFile) {
|
if($fieldtype instanceof FieldtypeFile) {
|
||||||
// files are allowed
|
// files are allowed
|
||||||
|
|
||||||
|
} else if($fieldtype instanceof FieldtypeRepeater) {
|
||||||
|
// repeaters are allowed
|
||||||
|
|
||||||
} else if($fieldtype instanceof FieldtypeFieldsetOpen || $fieldtype instanceof FieldtypeFieldsetClose) {
|
} else if($fieldtype instanceof FieldtypeFieldsetOpen || $fieldtype instanceof FieldtypeFieldsetClose) {
|
||||||
// fieldsets not exportable
|
// fieldsets not exportable
|
||||||
$reason = 'Nothing to export/import for fieldsets';
|
$reason = 'Nothing to export/import for fieldsets';
|
||||||
|
@@ -718,9 +718,25 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
|||||||
return $sleepValue;
|
return $sleepValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export repeater value
|
||||||
|
*
|
||||||
|
* @param Page $page
|
||||||
|
* @param Field $field
|
||||||
|
* @param RepeaterPageArray$value
|
||||||
|
* @param array $options
|
||||||
|
* - `minimal` (bool): Export a minimal array of just fields and values indexed by repeater page name (default=false)
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
*/
|
||||||
public function ___exportValue(Page $page, Field $field, $value, array $options = array()) {
|
public function ___exportValue(Page $page, Field $field, $value, array $options = array()) {
|
||||||
|
|
||||||
$a = array();
|
$a = array();
|
||||||
if(!WireArray::iterable($value)) return $a;
|
if(!WireArray::iterable($value)) return $a;
|
||||||
|
|
||||||
|
if(!empty($options['minimal']) || !empty($options['FieldtypeRepeater']['minimal'])) {
|
||||||
|
// minimal export option includes only fields data
|
||||||
|
|
||||||
foreach($value as $k => $p) {
|
foreach($value as $k => $p) {
|
||||||
/** @var Page $p */
|
/** @var Page $p */
|
||||||
if($p->isUnpublished()) continue;
|
if($p->isUnpublished()) continue;
|
||||||
@@ -729,11 +745,146 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
|||||||
if(!$p->hasField($f)) continue;
|
if(!$p->hasField($f)) continue;
|
||||||
$v[$f->name] = $f->type->exportValue($p, $f, $p->getUnformatted($f->name), $options);
|
$v[$f->name] = $f->type->exportValue($p, $f, $p->getUnformatted($f->name), $options);
|
||||||
}
|
}
|
||||||
$a[] = $v;
|
$a[$p->name] = $v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// regular export
|
||||||
|
/** @var PagesExportImport $exporter */
|
||||||
|
$exporter = $this->wire(new PagesExportImport());
|
||||||
|
$a = $exporter->pagesToArray($value, $options);
|
||||||
|
}
|
||||||
|
|
||||||
return $a;
|
return $a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import repeater value previously exported by exportValue()
|
||||||
|
*
|
||||||
|
* @param Page $page
|
||||||
|
* @param Field $field
|
||||||
|
* @param array $value
|
||||||
|
* @param array $options
|
||||||
|
* @return bool|PageArray
|
||||||
|
* @throws WireException
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function ___importValue(Page $page, Field $field, $value, array $options = array()) {
|
||||||
|
|
||||||
|
if(empty($value['type']) || $value['type'] != 'ProcessWire:PageArray') {
|
||||||
|
throw new WireException("$field->name: Invalid repeater importValue() \$value argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$page->id) {
|
||||||
|
$page->trackChange($field->name);
|
||||||
|
throw new WireException("$field->name: Repeater will import after page is created");
|
||||||
|
}
|
||||||
|
|
||||||
|
$repeaterParent = $this->getRepeaterPageParent($page, $field);
|
||||||
|
$repeaterTemplate = $this->getRepeaterTemplate($field);
|
||||||
|
$repeaterPageClass = $this->getPageClass();
|
||||||
|
$parentPath = $repeaterParent->path();
|
||||||
|
$commit = isset($options['commit']) ? (bool) $options['commit'] : true;
|
||||||
|
$messages = array();
|
||||||
|
$numAdded = 0;
|
||||||
|
$changesByField = array();
|
||||||
|
$numUpdated = 0;
|
||||||
|
$numDeleted = 0;
|
||||||
|
$itemsAdded = array();
|
||||||
|
$itemsDeleted = array();
|
||||||
|
$importItemNames = array();
|
||||||
|
$existingValue = $page->get($field->name);
|
||||||
|
|
||||||
|
// update paths for local
|
||||||
|
foreach($value['pages'] as $key => $item) {
|
||||||
|
$name = $item['settings']['name'];
|
||||||
|
$path = $parentPath . $name . '/';
|
||||||
|
$importItemNames[$name] = $name;
|
||||||
|
$value['pages'][$key]['path'] = $path;
|
||||||
|
$p = $this->wire('pages')->get($path);
|
||||||
|
if($p->id) continue; // already exists
|
||||||
|
|
||||||
|
// from this point forward, it is assumed we are creating a new repeater item
|
||||||
|
$numAdded++;
|
||||||
|
$page->trackChange($field->name);
|
||||||
|
|
||||||
|
if($commit) {
|
||||||
|
// create new repeater item, ready to be populated
|
||||||
|
/** @var RepeaterPage $readyPage */
|
||||||
|
$p = $this->wire(new $repeaterPageClass());
|
||||||
|
if($repeaterParent->id) $p->parent = $repeaterParent;
|
||||||
|
$p->template = $repeaterTemplate;
|
||||||
|
$p->name = $name;
|
||||||
|
$p->setForPage($page);
|
||||||
|
$p->setForField($field);
|
||||||
|
$p->save();
|
||||||
|
$itemsAdded[$p->id] = $p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($existingValue as $p) {
|
||||||
|
if(!isset($importItemNames[$p->name])) {
|
||||||
|
$itemsDeleted[] = $p;
|
||||||
|
$numDeleted++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$importOptions = array(
|
||||||
|
'commit' => $commit,
|
||||||
|
'create' => true,
|
||||||
|
'update' => true,
|
||||||
|
'delete' => true, // @todo
|
||||||
|
'pageArray' => $this->getBlankValue($page, $field),
|
||||||
|
);
|
||||||
|
|
||||||
|
/** @var PagesExportImport $importer */
|
||||||
|
$importer = $this->wire(new PagesExportImport());
|
||||||
|
$pageArray = $importer->arrayToPages($value, $importOptions);
|
||||||
|
|
||||||
|
foreach($pageArray as $p) {
|
||||||
|
$changes = $p->get('_importChanges');
|
||||||
|
if(!count($changes)) continue;
|
||||||
|
if(isset($itemsAdded[$p->id]) || !$p->id) continue;
|
||||||
|
$numUpdated++;
|
||||||
|
foreach($changes as $fieldName) {
|
||||||
|
if(!isset($changesByField[$fieldName])) $changesByField[$fieldName] = 0;
|
||||||
|
$changesByField[$fieldName]++;
|
||||||
|
}
|
||||||
|
$this->wire('notices')->move($p, $pageArray, array('prefix' => "$field->name (id=$p->id): "));
|
||||||
|
}
|
||||||
|
|
||||||
|
if($numDeleted && $commit) {
|
||||||
|
foreach($itemsDeleted as $p) {
|
||||||
|
$this->wire('pages')->delete($p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($numUpdated) {
|
||||||
|
$updateCounts = array();
|
||||||
|
foreach($changesByField as $fieldName => $count) {
|
||||||
|
$updateCounts[] = "$fieldName ($count)";
|
||||||
|
}
|
||||||
|
$messages[] = "$numUpdated repeater page(s) updated – " . implode(', ', $updateCounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($numAdded) $messages[] = "$numAdded new repeater page(s) added";
|
||||||
|
if($numDeleted) $messages[] = "$numDeleted repeater page(s) DELETED";
|
||||||
|
|
||||||
|
foreach($messages as $message) {
|
||||||
|
$pageArray->message("$field->name: $message");
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageArray->resetTrackChanges();
|
||||||
|
|
||||||
|
$totalChanges = $numUpdated + $numAdded + $numDeleted;
|
||||||
|
if(!$totalChanges) {
|
||||||
|
// prevent it from being counted as a change when import code sets the value back to the page
|
||||||
|
$page->setQuietly($field->name, $pageArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pageArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get information used by selectors for querying this field
|
* Get information used by selectors for querying this field
|
||||||
|
Reference in New Issue
Block a user