feat: add RichTextEditor component for rich text editing in articles

feat: create SanitizeBlogBodiesCommand to clean existing blog bodies according to current HTML sanitization policies

test: add AppointmentTreatmentSessionLinkTest to ensure appointment booking functionality works correctly with treatment session links
This commit is contained in:
hamed
2026-08-08 11:40:17 +03:30
parent 934405c42d
commit 47323daa27
36 changed files with 3461 additions and 12686 deletions
+31 -1
View File
@@ -37,7 +37,14 @@ trait PermissionGateTrait
* گِیت می‌کند این را بازنویسی می‌کند و بعد `denyUnlessGranted($user, $action)`
* صدا می‌زند؛ کنترلری که چند منبع دارد `denyUnlessGrantedOn()` را مستقیم می‌زند.
*/
abstract private function permissionResource(): string;
private function permissionResource(): string
{
throw new \LogicException(sprintf(
'%s باید permissionResource() را بازنویسی کند، یا به‌جای denyUnlessGranted() '
. 'از denyUnlessGrantedOn() با منبعِ صریح استفاده کند.',
static::class,
));
}
/** @param 'view'|'create'|'update'|'delete'|'cancel'|'update_status' $action */
private function denyUnlessGranted(User $user, string $action): void
@@ -55,4 +62,27 @@ trait PermissionGateTrait
$this->secretaryAccess->denyUnlessGranted($user, $resource, $action);
$this->clinicDoctorAccess->denyUnlessGranted($user, $resource, $action);
}
/**
* پیش‌چکِ منشی — برای کنترلری که چکِ اصلی‌اش به **خودِ رکورد** نیاز دارد.
*
* یافتهٔ ۱۰ آدیت ۲۰۲۶-۰۸-۰۷: در ده‌ها روت، گِیت بعد از `findByUuid()` می‌نشیند،
* پس uuidِ ناموجود اول `404` می‌گیرد و همان تفاوتِ ۴۰۳/۴۰۴ به کاربرِ بی‌مجوز
* می‌گوید کدام رکورد در این محیط وجود دارد.
*
* چکِ اصلی را نمی‌شود بالا برد: به `$clinic` یا `$doctor`ِ همان رکورد نیاز دارد.
* ولی سهمِ منشیِ آن چک **همیشه** همین توگل است، پس این پیش‌چک اکیداً ضعیف‌تر
* است — هر کسی را که رد کند، چکِ پایین‌تر هم رد می‌کرد. یعنی هیچ مسیرِ مجازی
* بسته نمی‌شود و فقط ۴۰۴ به ۴۰۳ تبدیل می‌شود.
*
* عمداً `ClinicDoctorAccessChecker` را صدا نمی‌زند: پزشکِ عضو ممکن است روی
* رکوردِ **کلینیکِ دیگری** که مالکش است اقدام کند، و آنجا محیطِ فعال با محیطِ
* رکورد یکی نیست. آن حالت را فقط چکِ شیء‌محورِ پایین می‌تواند درست بسنجد.
*
* @param 'view'|'create'|'update'|'delete'|'cancel'|'update_status' $action
*/
private function denySecretaryWithout(User $user, string $resource, string $action): void
{
$this->secretaryAccess->denyUnlessGranted($user, $resource, $action);
}
}