Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/dev 212 errors summary #12125

Merged
merged 49 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
92c9662
errors summary for entries and categories
i-just Oct 12, 2022
5991ef3
mutisite - validate for all sites when publishing
i-just Oct 12, 2022
2393ef1
errors summary class value update (consistency)
i-just Oct 12, 2022
1b73427
errors summary in slideouts (heading to be done)
i-just Oct 12, 2022
d9dcfa5
tweaks; error summary heading translatable
i-just Oct 12, 2022
fba6d41
Merge branch '4.3' into feature/dev-212-errors-summary
brandonkelly Oct 17, 2022
2de8b27
adjustments to cross site validation
i-just Oct 17, 2022
673db9f
amends to cross site validation logic
i-just Oct 19, 2022
e49a245
basic styling for errors summary
i-just Nov 8, 2022
28c0ed5
Update src/web/assets/cp/src/css/_cp.scss
i-just Nov 9, 2022
13e45a9
Update src/web/assets/cp/src/css/_cp.scss
i-just Nov 9, 2022
2c31290
Update src/web/assets/cp/src/css/_cp.scss
i-just Nov 9, 2022
7b5ebd0
Update src/web/assets/cp/src/css/_cp.scss
i-just Nov 9, 2022
8a14e67
Merge branch 'develop' into feature/dev-212-errors-summary
i-just Nov 9, 2022
2f9b9d2
errors summary accessibility improvements WIP
i-just Nov 9, 2022
2006c68
#12290 fixes & slideout errors summary accessibility
i-just Nov 10, 2022
344413c
bug fixes
i-just Nov 10, 2022
8352178
bug fix
i-just Nov 11, 2022
596e77d
Merge branch 'develop' into feature/dev-212-errors-summary
i-just Nov 23, 2022
022041e
Merge branch '4.4' into feature/dev-212-errors-summary
i-just Nov 23, 2022
d4c563e
href attr for error summary links
i-just Nov 24, 2022
8ac8cb7
amend anchoring errors in summary
i-just Nov 29, 2022
09ecb9a
FieldTest > testBlocks update after adding tabindex
i-just Nov 29, 2022
32d7d24
Update src/services/Elements.php
i-just Dec 1, 2022
b5a28bb
also target fields without "fields-" in data-attribute
i-just Dec 1, 2022
5b2833c
wrap attr name in span in error summary msgs
i-just Jan 11, 2023
bb32024
adjust for native fields; css fixes
i-just Jan 12, 2023
0d2d71b
don't show related element's errors
i-just Jan 12, 2023
3f41ec1
adjustment for custom field labels
i-just Jan 13, 2023
828820b
different approach to wrapping attr name
i-just Jan 16, 2023
f2fac0e
Update src/web/assets/cp/src/css/_main.scss
i-just Jan 17, 2023
393c156
recompiled css
i-just Jan 17, 2023
ebb13dd
change to related element validation message
i-just Jan 25, 2023
3e2d301
remove unused function
i-just Mar 15, 2023
c5bf440
Merge branch 'develop' into feature/dev-212-errors-summary
i-just Mar 15, 2023
b469896
user element test updated
i-just Mar 15, 2023
6b2b677
Merge branch 'develop' into feature/dev-212-errors-summary
i-just Mar 30, 2023
e8a2175
adjust for new tab ids
i-just Mar 30, 2023
e18a4b2
slideout tab dropdown menu - errors indicator
i-just Mar 30, 2023
e549669
compiled assets
i-just Mar 30, 2023
64f8d0d
Merge branch '4.5' into feature/dev-212-errors-summary
brandonkelly Aug 9, 2023
acd274d
Merge branch '4.5' into feature/dev-212-errors-summary
brandonkelly Aug 9, 2023
ba3041a
Fix JS error
brandonkelly Aug 9, 2023
1c32605
Cleanup + remove error summary from slideouts
brandonkelly Aug 9, 2023
b1914fd
const
brandonkelly Aug 9, 2023
f3361e7
Remove getNativeFieldByAttribute() - not used
brandonkelly Aug 9, 2023
7053d78
`@since` tags
brandonkelly Aug 9, 2023
8869a1e
errorsSummary → errorSummary
brandonkelly Aug 9, 2023
1c2368b
Release notes
brandonkelly Aug 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- `craft\base\Element::includeSetStatusAction()` now returns `false` by default regardless of what `hasStatuses()` returns, fixing a bug where some element indexes were unexpectedly getting “Set Status” actions.
- Query conditions generated by `craft\helpers\Db::parseParam()` no longer account for `null` values, due to a heavy performance impact. ([#11931](https://github.com/craftcms/cms/issues/11931))
- Fixed a bug where the default MySQL backup command would attempt to use credentials from `~/.my.cnf` if it exists, instead of Craft’s configured database connection settings. ([#12349](https://github.com/craftcms/cms/issues/12349))
- Updated Yii to 2.0.47.

## 4.3.3 - 2022-11-17
Expand Down
24 changes: 24 additions & 0 deletions src/base/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -4951,6 +4951,30 @@ protected function fieldLayoutFields(bool $visibleOnly = false): array
return [];
}

/**
* @inheritdoc
* @throws InvalidConfigException
*/
public function getNativeFieldByAttribute(string $attribute): ?BaseField
{
if (empty($attribute)) {
return null;
}

$fieldLayout = $this->getFieldLayout();
if ($fieldLayout) {
$field = array_values(array_filter(
$fieldLayout->getAvailableNativeFields(),
fn($nativeField) => $nativeField->attribute() == $attribute
));
if (!empty($field)) {
return $field[0];
}
}

return null;
}

/**
* @inheritdoc
* @throws InvalidConfigException if [[siteId]] is invalid
Expand Down
9 changes: 9 additions & 0 deletions src/base/ElementInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use craft\elements\db\ElementQueryInterface;
use craft\elements\User;
use craft\errors\InvalidFieldException;
use craft\fieldlayoutelements\BaseField;
use craft\models\FieldLayout;
use craft\models\Site;
use Illuminate\Support\Collection;
Expand Down Expand Up @@ -1609,4 +1610,12 @@ public function afterMoveInStructure(int $structureId): void;
* Returns the string representation of the element.
*/
public function __toString(): string;

/**
* Return the first native field for this element matched by the field attribute (~handle)
*
* @param string $attribute
* @return BaseField|null
*/
public function getNativeFieldByAttribute(string $attribute): ?BaseField;
}
123 changes: 121 additions & 2 deletions src/controllers/ElementsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use craft\web\CpScreenResponseBehavior;
use craft\web\View;
use Throwable;
use yii\helpers\Markdown;
use yii\web\BadRequestHttpException;
use yii\web\ForbiddenHttpException;
use yii\web\Response;
Expand Down Expand Up @@ -411,6 +412,7 @@ public function actionEdit(?ElementInterface $element, ?int $elementId = null):
$isDraft
))
->notice($element->isProvisionalDraft ? fn() => $this->_draftNotice() : null)
->errorsSummary(fn() => $this->_errorsSummary($element))
->prepareScreen(
fn(Response $response, string $containerId) => $this->_prepareEditor(
$element,
Expand Down Expand Up @@ -810,6 +812,122 @@ private function _editorContent(
return $html;
}

/**
* Return html for errors summary box
*
* @param ElementInterface $element
* @return string
*/
private function _errorsSummary(ElementInterface $element): string
{
$html = '';

if ($element->hasErrors()) {
$allErrors = $element->getErrors();
$allKeys = array_keys($allErrors);

// only show "top-level" errors
// if you e.g. have an assets field which is set to validate related assets,
// you should only see the top-level "Fix validation errors on the related asset" error
// and not the details of what's wrong with the selected asset;
foreach ($allKeys as $key) {
$lastNestedKey = substr_replace($key, '', strrpos($key, '.'));
$lastNestedKey = substr_replace($lastNestedKey, '', strrpos($lastNestedKey, '['));
if (!empty($lastNestedKey)) {
if (in_array($lastNestedKey, $allKeys)) {
unset($allErrors[$key]);
}
}
}
$errorsList = [];
foreach ($allErrors as $key => $errors) {
foreach ($errors as $error) {
$errorItem = Html::beginTag('li');

// this is true in case of e.g. cross site validation error
if (preg_match('/^\s?\<a /', $error)) {
$errorItem .= $error;
} else {
$error = Markdown::processParagraph(htmlspecialchars($error));
$errorItem .= Html::a(Craft::t('app', $error), '#', [
'data-field-error-key' => $key,
]);
}

$errorItem .= Html::endTag('li');

$errorsList[] = $errorItem;
}
}

if (!empty($errorsList)) {
$heading = Craft::t('app', 'Found {num, number} {num, plural, =1{error} other{errors}}:', [
'num' => count($errorsList),
]);

$html = Html::beginTag('div', [
'class' => ['errors-summary'],
'tabindex' => '-1',
]) .
Html::beginTag('div') .
Html::tag('span', '', [
'class' => 'notification-icon',
'data-icon' => 'alert',
'aria-label' => 'error',
'role' => 'img',
]) .
Html::tag('h2', $heading) .
Html::endTag('div') .
Html::beginTag('ul', [
'class' => ['errors'],
]) .
implode('', $errorsList) .
Html::endTag('ul') .
Html::endTag('div');
}
}

return $html;
}

/**
* Returns array of <li> items for error given handle
*
* @param array $allErrors
* @param string $handle
* @param string|null $label
* @return array
*/
private function _prepErrorsSummaryList(array $allErrors, string $handle, ?string $label = null): array
{
$errorsList = [];
if (isset($allErrors[$handle])) {
foreach ($allErrors[$handle] as $error) {
$errorItem = Html::beginTag('li');

// get name of the field; wrap that name in the error message in a span
if (!empty($label) && str_contains($error, $label)) {
$error = str_replace($label, "<span>$label</span>", $error);
}

// this is true in case of e.g. cross site validation error
if (preg_match('/^\s?\<a /', $error)) {
$errorItem .= $error;
} else {
$errorItem .= Html::a(Craft::t('app', $error), '#', [
'data-field-error-key' => $handle,
]);
}

$errorItem .= Html::endTag('li');

$errorsList[$handle][] = $errorItem;
}
}

return $errorsList;
}

private function _editorSidebar(
ElementInterface $element,
bool $mergedCanonicalChanges,
Expand Down Expand Up @@ -898,7 +1016,7 @@ public function actionSave(): ?Response
}

try {
$success = $elementsService->saveElement($element);
$success = $elementsService->saveElement($element, crossSiteValidate: Craft::$app->getIsMultiSite());
} catch (UnsupportedSiteException $e) {
$element->addError('siteId', $e->getMessage());
$success = false;
Expand Down Expand Up @@ -1307,7 +1425,7 @@ public function actionApplyDraft(): ?Response
$element->setScenario(Element::SCENARIO_LIVE);
}

if (!$elementsService->saveElement($element)) {
if (!$elementsService->saveElement($element, crossSiteValidate: Craft::$app->getIsMultiSite())) {
return $this->_asAppyDraftFailure($element);
}

Expand Down Expand Up @@ -1799,6 +1917,7 @@ private function _asFailure(ElementInterface $element, string $message): ?Respon
'modelName' => 'element',
'element' => $element->toArray($element->attributes()),
'errors' => $element->getErrors(),
'errorsSummary' => $this->_errorsSummary($element),
];

return $this->asFailure($message, $data, ['element' => $element]);
Expand Down
2 changes: 1 addition & 1 deletion src/db/mysql/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public function createColumnSchemaBuilder($type, $length = null): ColumnSchemaBu
public function getDefaultBackupCommand(?array $ignoreTables = null): string
{
$defaultArgs =
' --defaults-extra-file="' . $this->_createDumpConfigFile() . '"' .
' --defaults-file="' . $this->_createDumpConfigFile() . '"' .
' --add-drop-table' .
' --comments' .
' --create-options' .
Expand Down
1 change: 1 addition & 0 deletions src/helpers/Cp.php
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ public static function fieldHtml(string $input, array $config = []): string
'data' => [
'attribute' => $attribute,
],
'tabindex' => -1,
],
$config['fieldAttributes'] ?? []
)) .
Expand Down
11 changes: 11 additions & 0 deletions src/i18n/I18N.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,17 @@ public function translate($category, $message, $params, $language): ?string
return $translation;
}

/**
* @inheritdoc
*/
public function format($message, $params, $language)
{
// wrap attribute value in an <em> tag
array_walk($params, fn(&$val, $key) => ($key == 'attribute') ? $val = "*$val*" : $val);

return parent::format($message, $params, $language);
}

/**
* Returns whether [[translate()]] should wrap translations with `@` characters,
* per the `translationDebugOutput` config setting.
Expand Down
Loading