mirror of
https://github.com/processwire/processwire.git
synced 2025-08-24 07:13:08 +02:00
Fix issue processwire/processwire-issues#639 where template configured to unpublish when missing required field was not applying to missing required fields within repeater items.
This commit is contained in:
@@ -966,6 +966,34 @@ class InputfieldWrapper extends Inputfield implements \Countable, \IteratorAggre
|
||||
return $empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Inputfields in this wrapper that are required and have empty values
|
||||
*
|
||||
* This method includes all children up through the tree, not just direct children.
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param bool $required Only include empty Inputfields that are required? (default=true)
|
||||
* @return array of Inputfield instances indexed by name attributes
|
||||
*
|
||||
*/
|
||||
public function getEmpty($required = true) {
|
||||
$a = array();
|
||||
static $n = 0;
|
||||
foreach($this->children as $child) {
|
||||
if($child instanceof InputfieldWrapper) {
|
||||
$a = array_merge($a, $child->getEmpty($required));
|
||||
} else {
|
||||
if($required && !$child->getSetting('required')) continue;
|
||||
if(!$child->isEmpty()) continue;
|
||||
$name = $child->attr('name');
|
||||
if(empty($name)) $name = "_unknown" . (++$n);
|
||||
$a[$name] = $child;
|
||||
}
|
||||
}
|
||||
return $a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of errors that occurred on any of the children during input processing.
|
||||
*
|
||||
|
@@ -82,6 +82,14 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
*/
|
||||
protected $renderValueMode = false;
|
||||
|
||||
/**
|
||||
* Number of required empty Inputfields after processing
|
||||
*
|
||||
* @var int
|
||||
*
|
||||
*/
|
||||
protected $numRequiredEmpty = 0;
|
||||
|
||||
/**
|
||||
* Set config defaults
|
||||
*
|
||||
@@ -271,14 +279,18 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
$label = $this->field->getLabel();
|
||||
if(!$label) $label = ucfirst($this->field->name);
|
||||
|
||||
// remember which repeater items are open, when enabled
|
||||
// remember which repeater items are open (as stored in cookie), when enabled
|
||||
$openIDs = array();
|
||||
if((int) $this->field->get('rememberOpen')) {
|
||||
$this->addClass('InputfieldRepeaterRememberOpen', 'wrapClass');
|
||||
$openIDs = $this->wire('input')->cookie('repeaters_open');
|
||||
$openIDs = $this->wire('input')->cookie('repeaters_open');
|
||||
if($openIDs) $openIDs = explode('|', trim($openIDs, '|'));
|
||||
if(!is_array($openIDs)) $openIDs = array();
|
||||
} else {
|
||||
$openIDs = array();
|
||||
}
|
||||
// merge with any open IDs in session
|
||||
$_openIDs = $this->wire('session')->getFor($this, 'openIDs');
|
||||
if(is_array($_openIDs) && !empty($_openIDs)) {
|
||||
$openIDs = array_merge($openIDs, array_values($_openIDs));
|
||||
}
|
||||
|
||||
$minItems = $this->repeaterMinItems;
|
||||
@@ -754,6 +766,11 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
$sortChanged = false;
|
||||
$value->setTrackChanges(true);
|
||||
$pageIDs = array();
|
||||
$_openIDs = $this->wire('session')->getFor($this, 'openIDs');
|
||||
if(!is_array($_openIDs)) $_openIDs = array();
|
||||
$openIDs = $_openIDs; // these two are compared with each other at the end
|
||||
$this->numRequiredEmpty = 0;
|
||||
$this->getErrors(true);
|
||||
|
||||
// existing items
|
||||
foreach($value as $key => $page) {
|
||||
@@ -802,9 +819,11 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
/** @var InputfieldWrapper $wrapper */
|
||||
$wrapper = $this->wrappers[$page->id];
|
||||
$wrapper->resetTrackChanges(true);
|
||||
$wrapper->getErrors(true); // clear out any errors
|
||||
$wrapper->processInput($input);
|
||||
|
||||
$numErrors = count($wrapper->getErrors());
|
||||
$numRequiredEmpty = count($wrapper->getEmpty(true));
|
||||
$page->setQuietly('_repeater_errors', $numErrors); // signal to FieldtypeRepeater::savePageField() that page has errors
|
||||
$page->setQuietly('_repeater_processed', true); // signal to FieldtypeRepeater::savePageField() that page had input processed
|
||||
$this->formToPage($wrapper, $page);
|
||||
@@ -829,6 +848,14 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
$page->addStatus(Page::statusOn);
|
||||
}
|
||||
}
|
||||
|
||||
if($numErrors || $numRequiredEmpty) {
|
||||
$this->error(sprintf($this->_('Errors in “%s” item %d'), $this->label, $key + 1));
|
||||
if(!$page->hasStatus(Page::statusUnpublished)) $this->numRequiredEmpty += $numRequiredEmpty;
|
||||
$openIDs[$page->id] = $page->id; // force item with error to be open on next request
|
||||
} else if(isset($openIDs[$page->id])) {
|
||||
unset($openIDs[$page->id]);
|
||||
}
|
||||
|
||||
if($page->isChanged() && $this->page->id) $numChanges++;
|
||||
}
|
||||
@@ -858,6 +885,9 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
$this->page->trackChange($this->attr('name'));
|
||||
$this->trackChange('value');
|
||||
}
|
||||
|
||||
// if openIDs value changed, update the session variable
|
||||
if($_openIDs !== $openIDs) $this->wire('session')->setFor($this, 'openIDs', $openIDs);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -923,6 +953,33 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
||||
return $cnt === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return quantity of published items
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
*/
|
||||
public function numPublished() {
|
||||
/** @var PageArray $value */
|
||||
$value = $this->attr('value');
|
||||
if(empty($value) || !count($value)) return 0;
|
||||
$num = 0;
|
||||
foreach($value as $item) {
|
||||
if(!$item->hasStatus(Page::statusUnpublished)) $num++;
|
||||
}
|
||||
return $num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of required but empty Inputfields (across all repeater items)
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
*/
|
||||
public function numRequiredEmpty() {
|
||||
return $this->numRequiredEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default set() to capture the required $page variable that the repeaters field lives on.
|
||||
*
|
||||
|
@@ -1825,39 +1825,8 @@ class ProcessPageEdit extends Process implements WirePageEditor, ConfigurableMod
|
||||
|
||||
if($name == 'status' && $this->processInputStatus($inputfield)) continue;
|
||||
}
|
||||
|
||||
if($name && $errorAction
|
||||
&& $inputfield->getSetting('required') && $inputfield->isEmpty()
|
||||
&& !$inputfield->getSetting('requiredSkipped')
|
||||
&& !$this->page->isUnpublished()) {
|
||||
|
||||
if($errorAction === 1) {
|
||||
// restore existing value by skipping processing of empty when required
|
||||
$value = $inputfield->attr('value');
|
||||
if($value instanceof Wire) $value->resetTrackChanges();
|
||||
if($this->page->getField($name)) $this->page->remove($name); // force fresh copy to reload
|
||||
$previousValue = $this->page->get($name);
|
||||
$this->page->untrackChange($name);
|
||||
if($previousValue) {
|
||||
// we should have a previous value to restore
|
||||
if(WireArray::iterable($previousValue) && !count($previousValue)) {
|
||||
// previous value still empty
|
||||
} else {
|
||||
// previous value restored by simply not setting new value to $page
|
||||
$inputfield->error($this->_('Restored previous value'));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
} else if($errorAction === 2 && $this->page->publishable() && $this->page->id > 1) {
|
||||
// unpublish page missing required value
|
||||
$this->page->setQuietly('_forceAddStatus', Page::statusUnpublished);
|
||||
$label = $inputfield->getSetting('label');
|
||||
if(empty($label)) $label = $inputfield->attr('name');
|
||||
$this->error(sprintf($this->_('Page unpublished because field "%s" is required'), $label));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if($this->processInputErrorAction($this->page, $inputfield, $name, $errorAction)) continue;
|
||||
|
||||
if($name && $inputfield->isChanged()) {
|
||||
if($languages && $inputfield->getSetting('useLanguages')) {
|
||||
@@ -1887,6 +1856,72 @@ class ProcessPageEdit extends Process implements WirePageEditor, ConfigurableMod
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process required error actions as configured with page’s template
|
||||
*
|
||||
* @param Page $page
|
||||
* @param Inputfield|InputfieldRepeater $inputfield Inputfield that has already had its processInput() method called.
|
||||
* @param string $name Name of field that we are checking.
|
||||
* @param null|int $errorAction Error action from $page->template->errorAction, or omit to auto-detect.
|
||||
* @return bool Returns true if field $name should be skipped over during processing, or false if not
|
||||
*
|
||||
*/
|
||||
public function processInputErrorAction(Page $page, Inputfield $inputfield, $name, $errorAction = null) {
|
||||
|
||||
if(empty($name)) return false;
|
||||
if($errorAction === null) $errorAction = (int) $page->template->get('errorAction');
|
||||
if(!$errorAction) return false;
|
||||
if($page->isUnpublished()) return false;
|
||||
|
||||
$isRequired = $inputfield->getSetting('required');
|
||||
$isRepeater = strpos($inputfield->className(), 'Repeater') > 0 && wireInstanceOf($inputfield, 'InputfieldRepeater', false);
|
||||
|
||||
if(!$isRepeater && !$isRequired) return false;
|
||||
if($inputfield->getSetting('requiredSkipped')) return false;
|
||||
|
||||
if($isRepeater) {
|
||||
if($inputfield->numRequiredEmpty() > 0) {
|
||||
// repeater has required fields that are empty
|
||||
} else if($isRequired && $inputfield->numPublished() < 1) {
|
||||
// repeater is required and has no published items
|
||||
} else {
|
||||
// repeater is okay for now
|
||||
return false;
|
||||
}
|
||||
} else if(!$inputfield->isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($errorAction === 1) {
|
||||
// restore existing value by skipping processing of empty when required
|
||||
$value = $inputfield->attr('value');
|
||||
if($value instanceof Wire) $value->resetTrackChanges();
|
||||
if($page->getField($name)) $page->remove($name); // force fresh copy to reload
|
||||
$previousValue = $page->get($name);
|
||||
$page->untrackChange($name);
|
||||
if($previousValue) {
|
||||
// we should have a previous value to restore
|
||||
if(WireArray::iterable($previousValue) && !count($previousValue)) {
|
||||
// previous value still empty
|
||||
} else {
|
||||
// previous value restored by simply not setting new value to $page
|
||||
$inputfield->error($this->_('Restored previous value'));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} else if($errorAction === 2 && $page->publishable() && $page->id > 1) {
|
||||
// unpublish page missing required value
|
||||
$page->setQuietly('_forceAddStatus', Page::statusUnpublished);
|
||||
$label = $inputfield->getSetting('label');
|
||||
if(empty($label)) $label = $inputfield->attr('name');
|
||||
$inputfield->error(sprintf($this->_('Page unpublished because field "%s" is required'), $label));
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the page's created user has changed and make sure it's valid
|
||||
*
|
||||
|
Reference in New Issue
Block a user