mirror of
https://github.com/processwire/processwire.git
synced 2025-08-24 15:23:11 +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 $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.
|
* 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;
|
protected $renderValueMode = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of required empty Inputfields after processing
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected $numRequiredEmpty = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set config defaults
|
* Set config defaults
|
||||||
*
|
*
|
||||||
@@ -271,14 +279,18 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
|||||||
$label = $this->field->getLabel();
|
$label = $this->field->getLabel();
|
||||||
if(!$label) $label = ucfirst($this->field->name);
|
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')) {
|
if((int) $this->field->get('rememberOpen')) {
|
||||||
$this->addClass('InputfieldRepeaterRememberOpen', 'wrapClass');
|
$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($openIDs) $openIDs = explode('|', trim($openIDs, '|'));
|
||||||
if(!is_array($openIDs)) $openIDs = array();
|
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;
|
$minItems = $this->repeaterMinItems;
|
||||||
@@ -754,6 +766,11 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
|||||||
$sortChanged = false;
|
$sortChanged = false;
|
||||||
$value->setTrackChanges(true);
|
$value->setTrackChanges(true);
|
||||||
$pageIDs = array();
|
$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
|
// existing items
|
||||||
foreach($value as $key => $page) {
|
foreach($value as $key => $page) {
|
||||||
@@ -802,9 +819,11 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
|||||||
/** @var InputfieldWrapper $wrapper */
|
/** @var InputfieldWrapper $wrapper */
|
||||||
$wrapper = $this->wrappers[$page->id];
|
$wrapper = $this->wrappers[$page->id];
|
||||||
$wrapper->resetTrackChanges(true);
|
$wrapper->resetTrackChanges(true);
|
||||||
|
$wrapper->getErrors(true); // clear out any errors
|
||||||
$wrapper->processInput($input);
|
$wrapper->processInput($input);
|
||||||
|
|
||||||
$numErrors = count($wrapper->getErrors());
|
$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_errors', $numErrors); // signal to FieldtypeRepeater::savePageField() that page has errors
|
||||||
$page->setQuietly('_repeater_processed', true); // signal to FieldtypeRepeater::savePageField() that page had input processed
|
$page->setQuietly('_repeater_processed', true); // signal to FieldtypeRepeater::savePageField() that page had input processed
|
||||||
$this->formToPage($wrapper, $page);
|
$this->formToPage($wrapper, $page);
|
||||||
@@ -830,6 +849,14 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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++;
|
if($page->isChanged() && $this->page->id) $numChanges++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -859,6 +886,9 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
|||||||
$this->trackChange('value');
|
$this->trackChange('value');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if openIDs value changed, update the session variable
|
||||||
|
if($_openIDs !== $openIDs) $this->wire('session')->setFor($this, 'openIDs', $openIDs);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -923,6 +953,33 @@ class InputfieldRepeater extends Inputfield implements InputfieldItemList {
|
|||||||
return $cnt === 0;
|
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.
|
* Override the default set() to capture the required $page variable that the repeaters field lives on.
|
||||||
*
|
*
|
||||||
|
@@ -1826,38 +1826,7 @@ class ProcessPageEdit extends Process implements WirePageEditor, ConfigurableMod
|
|||||||
if($name == 'status' && $this->processInputStatus($inputfield)) continue;
|
if($name == 'status' && $this->processInputStatus($inputfield)) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($name && $errorAction
|
if($this->processInputErrorAction($this->page, $inputfield, $name, $errorAction)) continue;
|
||||||
&& $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($name && $inputfield->isChanged()) {
|
if($name && $inputfield->isChanged()) {
|
||||||
if($languages && $inputfield->getSetting('useLanguages')) {
|
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
|
* Check to see if the page's created user has changed and make sure it's valid
|
||||||
*
|
*
|
||||||
|
Reference in New Issue
Block a user