Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion src/Element/Conditions/RelatedToConditionRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use CraftCms\Cms\Element\Conditions\Contracts\ElementConditionRuleInterface;
use CraftCms\Cms\Element\Contracts\ElementInterface;
use CraftCms\Cms\Element\Queries\Contracts\ElementQueryInterface;
use CraftCms\Cms\Element\Validation\Rules\ElementTypeRule;
use CraftCms\Cms\Entry\Elements\Entry;
use CraftCms\Cms\Field\BaseRelationField;
use CraftCms\Cms\Field\Fields;
Expand Down Expand Up @@ -110,7 +111,7 @@ private function _elementTypeOptions(): array
public function getRules(): array
{
return array_merge(parent::getRules(), [
'elementType' => ['required', 'string'],
'elementType' => ['required', 'string', new ElementTypeRule],
]);
}

Expand Down
39 changes: 39 additions & 0 deletions src/Element/Validation/Rules/ElementTypeRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace CraftCms\Cms\Element\Validation\Rules;

use Closure;
use CraftCms\Cms\Component\ComponentHelper;
use CraftCms\Cms\Element\Contracts\ElementInterface;
use CraftCms\Cms\Element\Exceptions\InvalidTypeException;
use Illuminate\Contracts\Validation\ValidationRule;
use Stringable;

class ElementTypeRule implements ValidationRule
{
public static function isValid(mixed $value): bool
{
return is_string($value) && ComponentHelper::validateComponentClass($value, ElementInterface::class);
}

public static function message(mixed $value): string
{
$class = is_scalar($value) || $value instanceof Stringable
? (string) $value
: get_debug_type($value);

return new InvalidTypeException($class, ElementInterface::class)->getMessage();
}

#[\Override]
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (self::isValid($value)) {
return;
}

$fail(self::message($value));
}
}
3 changes: 2 additions & 1 deletion src/Http/Controllers/App/RenderController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use CraftCms\Cms\Element\Contracts\ElementInterface;
use CraftCms\Cms\Element\Drafts;
use CraftCms\Cms\Element\Queries\Contracts\NestedElementQueryInterface;
use CraftCms\Cms\Element\Validation\Rules\ElementTypeRule;
use CraftCms\Cms\Field\Data\MarkdownData;
use CraftCms\Cms\Field\Markdown as MarkdownField;
use CraftCms\Cms\Markdown\Markdown as MarkdownService;
Expand All @@ -37,7 +38,7 @@ public function elements(Request $request): JsonResponse
{
$criteria = $request->validate([
'elements' => ['required', 'array'],
'elements.*.type' => ['required', 'string'],
'elements.*.type' => ['required', 'string', new ElementTypeRule],
'elements.*.id' => ['required'],
'elements.*.siteId' => ['required'],
'elements.*.instances' => ['required', 'array'],
Expand Down
5 changes: 3 additions & 2 deletions src/Http/Controllers/Elements/DeleteElementsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use CraftCms\Cms\Element\Jobs\ReplaceReferences;
use CraftCms\Cms\Element\Jobs\ReplaceRelations;
use CraftCms\Cms\Element\Queries\Contracts\NestedElementQueryInterface;
use CraftCms\Cms\Element\Validation\Rules\ElementTypeRule;
use CraftCms\Cms\Field\FieldReferences;
use CraftCms\Cms\Http\Requests\ElementRequest;
use CraftCms\Cms\Http\RespondsWithFlash;
Expand Down Expand Up @@ -111,7 +112,7 @@ public function destroy(Elements $elementsService): JsonResponse
public function replaceRelationsModal(): CpModalResponse
{
$this->request->validate([
'sourceElementType' => ['required', 'string'],
'sourceElementType' => ['required', 'string', new ElementTypeRule],
]);

/** @var class-string<ElementInterface> $sourceElementType */
Expand Down Expand Up @@ -142,7 +143,7 @@ public function replaceRelationsModal(): CpModalResponse
public function replaceRelations(): Response
{
$this->request->validate([
'sourceElementType' => ['required', 'string'],
'sourceElementType' => ['required', 'string', new ElementTypeRule],
'newTargetId' => ['required', 'integer'],
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

namespace CraftCms\Cms\Http\Controllers\Elements\ElementIndex;

use Closure;
use CraftCms\Cms\Element\Contracts\ElementExporterInterface;
use CraftCms\Cms\Element\Contracts\ElementInterface;
use CraftCms\Cms\Element\ElementExporters;
use CraftCms\Cms\Element\Exceptions\InvalidTypeException;
use CraftCms\Cms\Element\Exporters\Raw;
use CraftCms\Cms\Element\Validation\Rules\ElementTypeRule;
use CraftCms\Cms\Http\Controllers\Elements\Concerns\InteractsWithElementIndexes;
use CraftCms\Cms\Http\Requests\ElementIndexRequest;
use Symfony\Component\HttpFoundation\Response;
Expand All @@ -29,11 +28,7 @@ public function __invoke(): Response
'elementType' => [
'required',
'string',
function (string $attribute, mixed $value, Closure $fail): void {
if (! is_string($value) || ! is_subclass_of($value, ElementInterface::class)) {
$fail(new InvalidTypeException((string) $value, ElementInterface::class)->getMessage());
}
},
new ElementTypeRule,
],
'type' => ['sometimes', 'string'],
'format' => ['sometimes', 'string'],
Expand Down
3 changes: 2 additions & 1 deletion src/Http/Controllers/FieldsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use CraftCms\Cms\Cp\Html\ContentHtml;
use CraftCms\Cms\Cp\Icons;
use CraftCms\Cms\Element\Contracts\ElementInterface;
use CraftCms\Cms\Element\Validation\Rules\ElementTypeRule;
use CraftCms\Cms\Field\Contracts\FieldInterface;
use CraftCms\Cms\Field\Enums\TranslationMethod;
use CraftCms\Cms\Field\Field;
Expand Down Expand Up @@ -336,7 +337,7 @@ private function fieldLayoutComponent(Request $request, ?array &$settings = null
{
$request->validate([
'uid' => ['required', 'string'],
'elementType' => ['required', 'string'],
'elementType' => ['required', 'string', new ElementTypeRule],
'layoutConfig' => ['required', 'array'],
'config' => ['nullable', 'array'],
'settings' => ['nullable', 'string'],
Expand Down
3 changes: 2 additions & 1 deletion src/Http/Controllers/MatrixController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use CraftCms\Cms\Element\Exceptions\InvalidElementException;
use CraftCms\Cms\Element\Queries\EntryQuery;
use CraftCms\Cms\Element\Validation\ElementRules;
use CraftCms\Cms\Element\Validation\Rules\ElementTypeRule;
use CraftCms\Cms\Entry\Elements\Entry;
use CraftCms\Cms\Entry\EntryTypes;
use CraftCms\Cms\Field\Matrix;
Expand Down Expand Up @@ -63,7 +64,7 @@ public function createEntry(Request $request): Response
'fieldId' => ['required'],
'entryTypeId' => ['required'],
'ownerId' => ['required'],
'ownerElementType' => ['required'],
'ownerElementType' => ['required', 'string', new ElementTypeRule],
'siteId' => ['required'],
'namespace' => ['required'],
'staticEntries' => ['nullable', 'boolean'],
Expand Down
3 changes: 2 additions & 1 deletion src/Http/Controllers/RelationalFieldsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use CraftCms\Cms\Element\Contracts\ElementInterface;
use CraftCms\Cms\Element\Drafts;
use CraftCms\Cms\Element\Validation\Rules\ElementTypeRule;
use CraftCms\Cms\Structure\Structures;
use CraftCms\Cms\Support\Facades\HtmlStack;
use Illuminate\Http\JsonResponse;
Expand All @@ -21,7 +22,7 @@ public function structuredInputHtml(
Structures $structures,
): JsonResponse {
$request->validate([
'elementType' => ['required', 'string'],
'elementType' => ['required', 'string', new ElementTypeRule],
'elementIds' => ['nullable', 'array'],
'siteId' => ['nullable'],
'branchLimit' => ['nullable', 'integer'],
Expand Down
9 changes: 2 additions & 7 deletions src/Http/Requests/ElementIndexRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@
namespace CraftCms\Cms\Http\Requests;

use Closure;
use CraftCms\Cms\Component\ComponentHelper;
use CraftCms\Cms\Element\Conditions\Contracts\ElementConditionInterface;
use CraftCms\Cms\Element\Conditions\ElementCondition;
use CraftCms\Cms\Element\Contracts\ElementInterface;
use CraftCms\Cms\Element\ElementSources;
use CraftCms\Cms\Element\Exceptions\InvalidTypeException;
use CraftCms\Cms\Element\Validation\Rules\ElementTypeRule;
use CraftCms\Cms\Support\Facades\Conditions;
use CraftCms\Cms\Support\Facades\Elements;
use Illuminate\Foundation\Http\FormRequest;
Expand All @@ -27,11 +26,7 @@ class ElementIndexRequest extends FormRequest
public function elementType(): string
{
$this->validate([
'elementType' => ['required', 'string', function (string $attribute, mixed $value, Closure $fail): void {
if (! ComponentHelper::validateComponentClass($value, ElementInterface::class)) {
$fail(new InvalidTypeException((string) $value, ElementInterface::class)->getMessage());
}
}],
'elementType' => ['required', 'string', new ElementTypeRule],
]);

return $this->input('elementType');
Expand Down
7 changes: 3 additions & 4 deletions src/Http/Requests/ElementRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

namespace CraftCms\Cms\Http\Requests;

use CraftCms\Cms\Component\ComponentHelper;
use CraftCms\Cms\Cp\RequestedSite;
use CraftCms\Cms\Element\Contracts\ElementInterface;
use CraftCms\Cms\Element\Exceptions\InvalidTypeException;
use CraftCms\Cms\Element\Queries\Contracts\ElementQueryInterface;
use CraftCms\Cms\Element\Queries\Contracts\NestedElementQueryInterface;
use CraftCms\Cms\Element\Validation\Rules\ElementTypeRule;
use CraftCms\Cms\Support\Arr;
use CraftCms\Cms\Support\Facades\Elements;
use CraftCms\Cms\Support\Facades\Sites;
Expand Down Expand Up @@ -188,11 +187,11 @@ private function elementQuery(bool $withNestedContext = true): ElementQueryInter

public function validateElementType(string $elementType): void
{
if (ComponentHelper::validateComponentClass($elementType, ElementInterface::class)) {
if (ElementTypeRule::isValid($elementType)) {
return;
}

abort(400, new InvalidTypeException($elementType, ElementInterface::class)->getMessage());
abort(400, ElementTypeRule::message($elementType));
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/Http/Requests/NestedElementsRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use CraftCms\Cms\Element\Contracts\NestedElementInterface;
use CraftCms\Cms\Element\ElementCollection;
use CraftCms\Cms\Element\Queries\Contracts\ElementQueryInterface;
use CraftCms\Cms\Element\Validation\Rules\ElementTypeRule;
use CraftCms\Cms\Support\Facades\Elements;
use Illuminate\Foundation\Http\FormRequest;

Expand All @@ -28,7 +29,7 @@ class NestedElementsRequest extends FormRequest
public function rules(): array
{
return [
'ownerElementType' => ['required', 'string'],
'ownerElementType' => ['required', 'string', new ElementTypeRule],
'ownerId' => ['required', 'integer'],
'ownerSiteId' => ['required', 'integer'],
'attribute' => ['required', 'string'],
Expand Down
3 changes: 2 additions & 1 deletion src/Image/Data/ImageTransform.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use CraftCms\Cms\Component\Component;
use CraftCms\Cms\Image\Contracts\ImageTransformerInterface;
use CraftCms\Cms\Image\ImageTransformer;
use CraftCms\Cms\Validation\Rules\HandleRule;
use DateTimeInterface;
use Illuminate\Validation\Rule;
use Override;
Expand Down Expand Up @@ -141,7 +142,7 @@ public function getRules(): array
{
return [
'name' => ['required', 'string'],
'handle' => ['required', 'string'],
'handle' => ['required', 'string', new HandleRule],
'width' => ['nullable', 'integer', 'min:1'],
'height' => ['nullable', 'integer', 'min:1'],
'mode' => ['required', Rule::in(self::MODES)],
Expand Down
5 changes: 3 additions & 2 deletions src/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use CraftCms\Cms\Update\Data\UpdateRelease;
use CraftCms\Cms\Update\Data\Updates as UpdatesData;
use CraftCms\Cms\User\Contracts\CraftUser;
use CraftCms\Cms\User\Validation\Rules\UserPasswordRule;
use GuzzleHttp\Utils;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Auth\SessionGuard;
Expand Down Expand Up @@ -55,9 +56,9 @@

class AppServiceProvider extends ServiceProvider
{
public static int $minPasswordLength = 8;
public static int $minPasswordLength = UserPasswordRule::MIN_PASSWORD_LENGTH;

public static int $maxPasswordLength = 160;
public static int $maxPasswordLength = UserPasswordRule::MAX_PASSWORD_LENGTH;

private string $root = __DIR__.'/../..';

Expand Down
3 changes: 2 additions & 1 deletion src/RouteToken/Data/RouteToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use CraftCms\Cms\Component\Component;
use CraftCms\Cms\Database\Table;
use CraftCms\Cms\Element\Contracts\ElementInterface;
use CraftCms\Cms\Element\Validation\Rules\ElementTypeRule;
use Illuminate\Validation\Rule;

class RouteToken extends Component
Expand Down Expand Up @@ -34,7 +35,7 @@ class RouteToken extends Component
public function getRules(): array
{
return [
'elementType' => ['required', 'string'],
'elementType' => ['required', 'string', new ElementTypeRule],
'siteId' => ['required', 'integer', Rule::exists(Table::SITES, 'id')],
'canonicalId' => ['nullable', 'required_without:sourceId'],
'sourceId' => ['nullable', 'required_without:canonicalId'],
Expand Down
2 changes: 1 addition & 1 deletion src/User/Validation/Rules/UserPasswordRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

readonly class UserPasswordRule implements ValidationRule
{
public const int MIN_PASSWORD_LENGTH = 6;
public const int MIN_PASSWORD_LENGTH = 8;

public const int MAX_PASSWORD_LENGTH = 160;

Expand Down
4 changes: 2 additions & 2 deletions tests/Feature/User/Elements/UserValidationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,8 @@ public function getFieldLayout(): ?FieldLayout

expect($user->errors()->has('newPassword'))->toBe($expectError);
})->with([
'5 chars is too short' => ['12345', true],
'6 chars is valid' => ['123456', false],
'7 chars is too short' => ['1234567', true],
'8 chars is valid' => ['12345678', false],
'160 chars is valid' => [str_repeat('a', 160), false],
'161 chars is too long' => [str_repeat('a', 161), true],
'null is valid' => [null, false],
Expand Down
28 changes: 28 additions & 0 deletions tests/Unit/Element/Validation/Rules/ElementTypeRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

use CraftCms\Cms\Element\Validation\Rules\ElementTypeRule;
use CraftCms\Cms\Entry\Elements\Entry;

it('validates element type class strings', function (mixed $input, bool $expected) {
$rule = new ElementTypeRule;
$valid = true;

$rule->validate('elementType', $input, function () use (&$valid) {
$valid = false;
});

expect($valid)->toBe($expected);
})->with([
'element type' => [Entry::class, true],
'missing class' => ['App\\MissingElement', false],
'non-element class' => [stdClass::class, false],
'integer' => [123, false],
'array' => [['type' => Entry::class], false],
]);

it('exposes the same validity check used by non-validator callers', function () {
expect(ElementTypeRule::isValid(Entry::class))->toBeTrue()
->and(ElementTypeRule::isValid(stdClass::class))->toBeFalse();
});
15 changes: 15 additions & 0 deletions tests/Unit/Image/Data/ImageTransformTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,21 @@
->and($transform->errors()->has('handle'))->toBeTrue();
});

it('requires valid handles', function (string $handle, bool $expected) {
$transform = new ImageTransform([
'name' => 'Test',
'handle' => $handle,
]);

expect($transform->validate())->toBe($expected);
})->with([
'camel case' => ['validHandle', true],
'underscore' => ['valid_handle', true],
'hyphen' => ['invalid-handle', false],
'leading number' => ['1invalid', false],
'reserved word' => ['handle', false],
]);

test('fails with invalid mode', function () {
$transform = new ImageTransform([
'name' => 'Test',
Expand Down
Loading
Loading