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:
@@ -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'];
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user