1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-09 16:26:59 +02:00

Add Repeater field support to new Page Export/Import features.

This commit is contained in:
Ryan Cramer
2017-08-15 12:13:25 -04:00
parent e89235b757
commit b5b2636e01
3 changed files with 209 additions and 45 deletions

View File

@@ -33,6 +33,7 @@
* @method mixed wakeupValue(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|object importValue(Page $page, Field $field, $value, array $options = array())
* @method bool createField(Field $field)
* @method array getSelectorInfo(Field $field, array $data = array())
* @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 == 'shortName') {
return str_replace('Fieldtype', '', $this->className());
} else if($key == 'longName') {
} else if($key == 'longName' && method_exists($this, 'getModuleInfo')) {
$info = $this->getModuleInfo($this);
return $info['title'];
}

View File

@@ -159,30 +159,38 @@ class PagesExportImport extends Wire {
// include information about field settings so that warnings can be generated at
// import time if there are applicable differences in the field settings
foreach($exportItem['data'] as $fieldName => $value) {
if(isset($a['fields'][$fieldName])) continue;
$field = $this->wire('fields')->get($fieldName);
if(!$field || !$field->type) continue;
$moduleInfo = $this->wire('modules')->getModuleInfoVerbose($field->type);
if($options['verbose']) {
$fieldData = $field->getExportData();
unset($fieldData['id'], $fieldData['name']);
$a['fields'][$fieldName] = $fieldData;
} else {
$a['fields'][$fieldName] = array(
'type' => $field->type->className(),
'label' => $field->label,
'version' => $moduleInfo['versionStr']
);
$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;
}
$blankValue = $field->type->getBlankValue($item, $field);
if(is_object($blankValue)) {
if($blankValue instanceof Wire) {
$blankValue = "class:" . $blankValue->className();
foreach($fieldNames as $fieldName) {
if(isset($a['fields'][$fieldName])) continue;
$field = $this->wire('fields')->get($fieldName);
if(!$field || !$field->type) continue;
$moduleInfo = $this->wire('modules')->getModuleInfoVerbose($field->type);
if($options['verbose']) {
$fieldData = $field->getExportData();
unset($fieldData['name']);
$a['fields'][$fieldName] = $fieldData;
} else {
$blankValue = "class:" . get_class($blankValue);
$a['fields'][$fieldName] = array(
'type' => $field->type->className(),
'label' => $field->label,
'version' => $moduleInfo['versionStr'],
'id' => $field->id
);
}
$blankValue = $field->type->getBlankValue($item, $field);
if(is_object($blankValue)) {
if($blankValue instanceof Wire) {
$blankValue = "class:" . $blankValue->className();
} else {
$blankValue = "class:" . get_class($blankValue);
}
}
$a['fields'][$fieldName]['blankValue'] = $blankValue;
}
$a['fields'][$fieldName]['blankValue'] = $blankValue;
}
// include information about template settings so that warnings can be generated
@@ -319,10 +327,15 @@ class PagesExportImport extends Wire {
$defaults = array(
'count' => false, // Return count of imported pages, rather than PageArray (reduced memory requirements)
'pageArray' => null,
);
$options = array_merge($defaults, $options);
$pageArray = $this->wire('pages')->newPageArray();
if(!empty($options['pageArray']) && $options['pageArray'] instanceof PageArray) {
$pageArray = $options['pageArray'];
} else {
$pageArray = $this->wire('pages')->newPageArray();
}
$count = 0;
// $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
$info = $this->getImportInfo($a);
if($info) {}
foreach($a['pages'] as $item) {
$page = $this->arrayToPage($item, $options);
$e = $page->errors('string clear');
$w = $page->warnings('string clear');
$id = $item['settings']['id'];
if(strlen($e)) foreach(explode("\n", $e) as $s) $pageArray->error("Page $id: $s");
if(strlen($w)) foreach(explode("\n", $w) as $s) $pageArray->warning("Page $id: $s");
$count++;
$this->wire('notices')->move($page, $pageArray, array('prefix' => "Page $id: "));
if(!$options['count']) $pageArray->add($page);
$count++;
}
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)
'update' => true, // allow update of existing 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)
'changeParent' => false,
'changeName' => true,
@@ -728,7 +740,8 @@ class PagesExportImport extends Wire {
if($field->type instanceof FieldtypeFile) {
// file fields (cannot be accessed until page exists)
if($page->id) {
return $this->importFileFieldValue($page, $field, $importValue, $options);
$this->importFileFieldValue($page, $field, $importValue, $options);
return;
} else if(!empty($importValue)) {
$page->trackChange($field->name);
}
@@ -737,6 +750,7 @@ class PagesExportImport extends Wire {
$o = array(
'system' => true,
'caller' => $this,
'commit' => $options['commit'],
'test' => !$options['commit']
);
@@ -745,6 +759,7 @@ class PagesExportImport extends Wire {
if(!$options['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 FieldtypeRepeater) $fakeCommit = true;
}
if($page->get('_importType') == 'create' && !$options['commit'] && !$fakeCommit) {
@@ -768,14 +783,8 @@ class PagesExportImport extends Wire {
$pageValue = $field->type->importValue($page, $field, $importValue, $o);
if($pageValue !== null) $page->set($field->name, $pageValue);
if(is_object($pageValue) && $pageValue instanceof Wire) {
// copy notices from the pageValue to the page
foreach(array('errors', 'warnings', 'messages') as $noticeType) {
foreach($pageValue->$noticeType('clear') as $notice) {
$method = rtrim($noticeType, 's');
$page->$method($notice->text);
$this->warning($notice->text);
}
}
// movie notices from the pageValue to the page
$this->wire('notices')->move($pageValue, $page);
}
} else {
// test import on existing page, avoids actually setting value to the page
@@ -1134,6 +1143,9 @@ class PagesExportImport extends Wire {
if($fieldtype instanceof FieldtypeFile) {
// files are allowed
} else if($fieldtype instanceof FieldtypeRepeater) {
// repeaters are allowed
} else if($fieldtype instanceof FieldtypeFieldsetOpen || $fieldtype instanceof FieldtypeFieldsetClose) {
// fieldsets not exportable

View File

@@ -718,20 +718,171 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
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()) {
$a = array();
if(!WireArray::iterable($value)) return $a;
foreach($value as $k => $p) {
/** @var Page $p */
if($p->isUnpublished()) continue;
$v = array();
foreach($p->template->fieldgroup as $f) {
if(!$p->hasField($f)) continue;
$v[$f->name] = $f->type->exportValue($p, $f, $p->getUnformatted($f->name), $options);
if(!empty($options['minimal']) || !empty($options['FieldtypeRepeater']['minimal'])) {
// minimal export option includes only fields data
foreach($value as $k => $p) {
/** @var Page $p */
if($p->isUnpublished()) continue;
$v = array();
foreach($p->template->fieldgroup as $f) {
if(!$p->hasField($f)) continue;
$v[$f->name] = $f->type->exportValue($p, $f, $p->getUnformatted($f->name), $options);
}
$a[$p->name] = $v;
}
$a[] = $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;
}