محتوای کامل...
", "image": "https://...", - "author": { "uuid": "...", "real_name": "دکتر احمدی" }, + "author": { "uuid": "...", "real_name": "احمدی" }, "tags": [{ "id": 1, "name": "دیابت" }], "status": "published", "created_at": 1717000000, diff --git a/docs/api/clinic-invitation.md b/docs/api/clinic-invitation.md index 1646967a..aac155e3 100644 --- a/docs/api/clinic-invitation.md +++ b/docs/api/clinic-invitation.md @@ -34,7 +34,7 @@ Send an invitation to a doctor (by mobile number) to join a clinic. ```json { "mobile": "09123456789", - "name": "دکتر علی احمدی", + "name": "علی احمدی", "specialty": "قلب و عروق" } ``` @@ -58,7 +58,7 @@ Send an invitation to a doctor (by mobile number) to join a clinic. "invited_at": 1717000000, "expires_at": 1717259200, "token_used": false, - "doctor": { "uuid": "...", "name": "دکتر علی احمدی" }, + "doctor": { "uuid": "...", "name": "علی احمدی" }, "clinic": { "uuid": "...", "name": "کلینیک الوند" } } } diff --git a/docs/api/clinic.md b/docs/api/clinic.md index 9ff6d638..a9e0ced3 100644 --- a/docs/api/clinic.md +++ b/docs/api/clinic.md @@ -8,7 +8,18 @@ Create a new clinic. -**Permission:** `AUTH` — any authenticated user becomes the clinic owner +**Permission:** `AUTH` — any authenticated user becomes the clinic owner, and is +granted `ROLE_CLINIC` on success. + +**One clinic per user.** `ClinicRepository::findByUser()` (which resolves the +caller's working context) is a `findOneBy`, so a second clinic would be +unreachable data — the request is rejected instead. + +**Errors:** +| Code | HTTP | توضیح | +|------|------|-------| +| ERR_VALIDATION_001 | 422 | `name` خالی است (field: `name`) | +| ERR_CONFLICT_001 | 409 | این کاربر از قبل مالک یک کلینیک است | ### Request Body (`application/json`) ```json @@ -295,7 +306,7 @@ Get doctors associated with a clinic. { "id": "1207", "uuid": "...", - "name": "دکتر آرمان رضایی", + "name": "آرمان رضایی", "gender": "man", "degree": "specialist", "img": [], @@ -401,7 +412,7 @@ List the permission rows of every doctor in the clinic. "uuid": "ce200cde-826d-11f1-b923-c282b864cdcc", "clinic_uuid": "41e325c4-e825-4067-8438-5d828ecaee09", "doctor_uuid": "bcabb3a8-cae3-45ec-876c-548f9c1e1569", - "doctor_name": "دکتر تست", + "doctor_name": "تست", "active": true, "permissions": { "version": 1, "resources": { "...": {} } }, "created_at": 1784352916, diff --git a/docs/api/doctor-claim.md b/docs/api/doctor-claim.md index fcd6a383..e96417ac 100644 --- a/docs/api/doctor-claim.md +++ b/docs/api/doctor-claim.md @@ -82,7 +82,7 @@ unclaimed ──claim/transfer شروع──▶ pending_transfer ──موفق "data": { "status": "claimed", "claim": { "uuid": "…" }, - "doctor": { "uuid": "…", "name": "دکتر فرخنده حسینی" } + "doctor": { "uuid": "…", "name": "فرخنده حسینی" } } } ``` @@ -127,7 +127,7 @@ unclaimed ──claim/transfer شروع──▶ pending_transfer ──موفق { "uuid": "…", "status": "completed", - "doctor": { "uuid": "…", "name": "دکتر فرخنده حسینی" }, + "doctor": { "uuid": "…", "name": "فرخنده حسینی" }, "mobile_masked": "0912***4567", "verification_method": "apiir_personinfo+shahkar", "failure_reason": null, diff --git a/docs/api/doctor-import.md b/docs/api/doctor-import.md index 085c7c2a..ff1c86ce 100644 --- a/docs/api/doctor-import.md +++ b/docs/api/doctor-import.md @@ -50,7 +50,7 @@ ```json { - "name": "دکتر فرخنده حسینی", + "name": "فرخنده حسینی", "medical_system_code": "145657", "source": "irimc", "source_ref": "https://membersearch.irimc.org/member/profile?id=…", diff --git a/docs/api/doctor.md b/docs/api/doctor.md index f3c2fcd0..deb2596b 100644 --- a/docs/api/doctor.md +++ b/docs/api/doctor.md @@ -24,7 +24,7 @@ Create a doctor profile for the authenticated user. ### Request Body (`application/json`) ```json { - "title": "دکتر علی احمدی", + "title": "علی احمدی", "gender": "male", "medical_system_code": "12345", "degree": "متخصص", @@ -36,7 +36,7 @@ Create a doctor profile for the authenticated user. | Field | Type | Required | Description | |-------|------|----------|-------------| -| `title` | string | ✅ | Full name with title | +| `title` | string | ✅ | نام پزشک **بدون** عنوان. پیشوند «دکتر» سمت سرور با `PersianText::stripDoctorTitle()` حذف میشود؛ نمایش عنوان کار لایهٔ UI است. | | `gender` | string | ❌ | `"male"` or `"female"` | | `medical_system_code` | string | ❌ | Nظام پزشکی code | | `degree` | string | ❌ | Academic degree | @@ -51,7 +51,7 @@ Create a doctor profile for the authenticated user. "success": true, "data": { "uuid": "550e8400-e29b-41d4-a716-446655440000", - "title": "دکتر علی احمدی", + "title": "علی احمدی", "gender": "male", "medical_system_code": "12345", "degree": "متخصص", @@ -93,7 +93,7 @@ Get doctor detail with clinics. "data": { "data": { "uuid": "550e8400-...", - "name": "دکتر علی احمدی", + "name": "علی احمدی", "gender": "man", "medical_system_code": "12345", "degree": "specialist", @@ -153,7 +153,7 @@ Get doctor detail for clinic owner — only doctors who are members of the authe "data": { "data": { "uuid": "...", - "title": "دکتر علی احمدی", + "title": "علی احمدی", "specialties": [...], "clinics": [{ "uuid": "...", "name": "کلینیک نور", "address": "...", "telephone": "..." }] } @@ -219,7 +219,7 @@ List doctors with pagination and filters. "data": [ { "uuid": "...", - "name": "دکتر علی احمدی", + "name": "علی احمدی", "gender": "man", "degree": "specialist", "img": [], diff --git a/docs/api/patient.md b/docs/api/patient.md index afabfd90..c15f436d 100644 --- a/docs/api/patient.md +++ b/docs/api/patient.md @@ -437,7 +437,7 @@ GET /api/v1/patient/{uuid}/appointments "ends_at": 1754001800, "status": "confirmed", "version": 1, - "doctor_name": "دکتر ژیلا فتحی", + "doctor_name": "ژیلا فتحی", "service_name": null, "price_rials": null, "created_at": 1754000000 diff --git a/docs/api/rating.md b/docs/api/rating.md index f474767c..504ca3cb 100644 --- a/docs/api/rating.md +++ b/docs/api/rating.md @@ -271,7 +271,7 @@ Get all pending comments waiting for review. { "uuid": "...", "body": "...", - "doctor": { "uuid": "...", "title": "دکتر علی احمدی" }, + "doctor": { "uuid": "...", "title": "علی احمدی" }, "user": { "uuid": "...", "real_name": "..." }, "status": "pending", "created_at": 1717000000 diff --git a/docs/api/representation.md b/docs/api/representation.md index c66e754a..55ddd9bd 100644 --- a/docs/api/representation.md +++ b/docs/api/representation.md @@ -380,7 +380,7 @@ Get yearly earnings dashboard for a representation. #### Request Body ```json -{ "mobile": "0935...", "name": "دکتر ...", "gender": "man", "degree": "...", "medical_system_code": "...", "specialties": [1,2] } +{ "mobile": "0935...", "name": "...", "gender": "man", "degree": "...", "medical_system_code": "...", "specialties": [1,2] } ``` | Field | Type | Required | |-------|------|----------| @@ -452,7 +452,7 @@ Get yearly earnings dashboard for a representation. "patient_name": "...", "patient_mobile": "0912...", "doctor_uuid": "...", - "doctor_name": "دکتر ...", + "doctor_name": "...", "slot_start": 1718000000, "slot_end": 1718001800, "appointment_date": "2025-06-15", @@ -491,7 +491,7 @@ Get yearly earnings dashboard for a representation. "success": true, "data": [ { - "uuid": "...", "id": 12, "name": "دکتر ...", "gender": "man", "degree": "...", + "uuid": "...", "id": 12, "name": "...", "gender": "man", "degree": "...", "medical_code": "...", "mobile": "0912...", "email": null, "is_active": true, "rate": 3.5, "specialties": [], "profile_image": null, "created_at": "2026-06-18T..." @@ -621,7 +621,7 @@ Get yearly earnings dashboard for a representation. "success": true, "data": [ { - "uuid": "...", "name": "دکتر ...", + "uuid": "...", "name": "...", "appointments": { "today": 0, "week": 1, "month": 4, "total": 18 }, "representation_income_rials": 363636, "subscription_status": "active" @@ -642,7 +642,7 @@ Get yearly earnings dashboard for a representation. "success": true, "data": [ { - "uuid": "...", "appointment_uuid": "...", "doctor_name": "دکتر ...", + "uuid": "...", "appointment_uuid": "...", "doctor_name": "...", "gross_rials": 2000000, "tax_rials": 45455, "sms_fee_rials": 1500000, "commission_percent": 20, "representation_share_rials": 90909, "created_at": "2026-06-24T..." diff --git a/docs/api/secretary.md b/docs/api/secretary.md index b6f0aa43..50c8352c 100644 --- a/docs/api/secretary.md +++ b/docs/api/secretary.md @@ -160,7 +160,7 @@ Create a secretary for a doctor. "uuid": "sec-uuid-...", "user_name": "علی محمدی", "mobile_number": "09123456789", - "doctor_name": "دکتر احمد رضایی", + "doctor_name": "احمد رضایی", "doctor_uuid": "...", "owner_type": "doctor", "clinic_uuid": null, @@ -215,7 +215,7 @@ Get secretary detail. "mobile_number": "09123456789", "active": true, "permissions": { ... }, - "doctor": { "uuid": "...", "title": "دکتر علی احمدی" }, + "doctor": { "uuid": "...", "title": "علی احمدی" }, "created_at": 1717000000 } } @@ -325,7 +325,7 @@ Get all secretaries for a specific doctor. "uuid": "...", "user_name": "علی محمدی", "mobile_number": "09...", - "doctor_name": "دکتر احمد رضایی", + "doctor_name": "احمد رضایی", "doctor_uuid": "...", "is_active": true, "permissions": { ... }, @@ -368,7 +368,7 @@ Get all secretaries across **all doctors** of a clinic. "secretary_uuid": "...", "user_name": "علی محمدی", "mobile_number": "09...", - "doctor_name": "دکتر احمد رضایی", + "doctor_name": "احمد رضایی", "doctor_uuid": "...", "is_active": true, "permissions": { ... }, diff --git a/docs/api/sms.md b/docs/api/sms.md index b0f44140..707db336 100644 --- a/docs/api/sms.md +++ b/docs/api/sms.md @@ -62,7 +62,7 @@ Send an SMS using an approved template. "mobile": "09123456789", "template_uuid": "tmpl-uuid-...", "vars": { - "name": "دکتر علی احمدی", + "name": "علی احمدی", "date": "۱۵ خرداد ۱۴۰۴" }, "provider": "kavenegar" diff --git a/src/Admin/Controller/AdminApiController.php b/src/Admin/Controller/AdminApiController.php index 7a27b5a3..f717ef6b 100644 --- a/src/Admin/Controller/AdminApiController.php +++ b/src/Admin/Controller/AdminApiController.php @@ -30,6 +30,7 @@ use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\CurrentUser; use Symfony\Component\Security\Http\Attribute\IsGranted; +use App\Shared\Util\PersianText; #[OA\Tag(name: 'Admin')] #[IsGranted('ROLE_ADMIN')] @@ -457,7 +458,7 @@ class AdminApiController extends BaseController { $data = json_decode($request->getContent(), true) ?? []; $mobile = InputValidator::toEnglishDigits(trim((string) ($data['mobile'] ?? ''))); - $name = trim((string) ($data['name'] ?? '')); + $name = PersianText::stripDoctorTitle((string) ($data['name'] ?? '')); if ($mobile === '' || $name === '') { return $this->error(ErrorCodes::VALIDATION, 'موبایل و نام الزامی هستند', 422); diff --git a/src/Clinic/Controller/ClinicController.php b/src/Clinic/Controller/ClinicController.php index 3b9b9fd0..4f785f0c 100644 --- a/src/Clinic/Controller/ClinicController.php +++ b/src/Clinic/Controller/ClinicController.php @@ -99,6 +99,19 @@ class ClinicController extends BaseController if (($err = $this->validateGallerySize($data)) !== null) { return $err; } + + // بدون نام، کلینیکِ بیهویت ساخته میشد که در هیچ لیستی قابلتشخیص نیست. + if (trim((string) ($data['name'] ?? '')) === '') { + return $this->error(ErrorCodes::ERR_VALIDATION_001, 'نام کلینیک الزامی است', 422, 'name'); + } + + // یک کلینیک به ازای هر کاربر: ClinicRepository::findByUser() — که محیط کاری + // کاربر از آن حل میشود — findOneBy است، پس کلینیک دوم به بعد هرگز انتخاب + // نمیشود و به دادهٔ یتیم تبدیل میشود. + if ($this->clinicRepo->findByUser($user) !== null) { + return $this->error(ErrorCodes::ERR_CONFLICT_001, 'برای این کاربر کلینیک ثبت شده است', 409); + } + $clinic = new Clinic($user); $this->hydrateClinic($clinic, $data); $this->clinicRepo->save($clinic); diff --git a/src/Doctor/Command/FixIrimcNamesCommand.php b/src/Doctor/Command/FixIrimcNamesCommand.php index 7a8ef8d5..5f6774fc 100644 --- a/src/Doctor/Command/FixIrimcNamesCommand.php +++ b/src/Doctor/Command/FixIrimcNamesCommand.php @@ -13,11 +13,14 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; /** - * اصلاح یکبارمصرف نامِ پزشکانِ ایمپورتشدهٔ IRIMC که با پیشوند «دکتر» ذخیره شدهاند. - * فقط source='irimc' را دست میزند؛ پزشکان manual/seed را تغییر نمیدهد. + * حذف پیشوند «دکتر» از نام پزشکانی که با عنوان ذخیره شدهاند. نام پزشک هرگز نباید + * عنوان داشته باشد؛ لایهٔ نمایش خودش تصمیم میگیرد چطور نشانش دهد. + * + * پیشفرض فقط source='irimc' است. `--all` هر منبعی (seed/manual) را هم پاک میکند — + * لازم است چون مسیرهای ثبتنام تا پیش از این عنوان را حذف نمیکردند. * * php bin/console app:doctors:fix-irimc-names --dry-run - * php bin/console app:doctors:fix-irimc-names + * php bin/console app:doctors:fix-irimc-names --all */ #[AsCommand( name: 'app:doctors:fix-irimc-names', @@ -33,19 +36,21 @@ class FixIrimcNamesCommand extends Command protected function configure(): void { $this->addOption('dry-run', null, InputOption::VALUE_NONE, 'Report only, change nothing'); + $this->addOption('all', null, InputOption::VALUE_NONE, 'Every doctor, not just source=irimc'); } protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $dryRun = (bool) $input->getOption('dry-run'); + $all = (bool) $input->getOption('all'); + $qb = $this->em->getRepository(Doctor::class)->createQueryBuilder('d'); + if (!$all) { + $qb->where('d.source = :src')->setParameter('src', 'irimc'); + } /** @var Doctor[] $doctors */ - $doctors = $this->em->getRepository(Doctor::class)->createQueryBuilder('d') - ->where('d.source = :src') - ->setParameter('src', 'irimc') - ->getQuery() - ->getResult(); + $doctors = $qb->getQuery()->getResult(); $fixed = 0; foreach ($doctors as $doctor) { @@ -63,7 +68,13 @@ class FixIrimcNamesCommand extends Command $this->em->flush(); } - $io->success(sprintf('%d نام %s (از %d پزشک IRIMC).', $fixed, $dryRun ? 'قابل اصلاح' : 'اصلاح شد', count($doctors))); + $io->success(sprintf( + '%d نام %s (از %d پزشک %s).', + $fixed, + $dryRun ? 'قابل اصلاح' : 'اصلاح شد', + count($doctors), + $all ? 'بررسیشده' : 'IRIMC', + )); return Command::SUCCESS; } diff --git a/src/Doctor/Controller/DoctorController.php b/src/Doctor/Controller/DoctorController.php index 3a67099b..712496fe 100644 --- a/src/Doctor/Controller/DoctorController.php +++ b/src/Doctor/Controller/DoctorController.php @@ -26,6 +26,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\CurrentUser; use Symfony\Component\Security\Http\Attribute\IsGranted; +use App\Shared\Util\PersianText; #[OA\Tag(name: 'Doctors')] class DoctorController extends BaseController @@ -105,7 +106,7 @@ class DoctorController extends BaseController } $data = json_decode($request->getContent(), true) ?? []; - $name = trim($data['title'] ?? $data['name'] ?? ''); + $name = PersianText::stripDoctorTitle($data['title'] ?? $data['name'] ?? ''); if ($name === '') { return $this->error(ErrorCodes::ERR_VALIDATION_001, 'نام دکتر الزامی است', 422, 'title'); @@ -350,7 +351,7 @@ class DoctorController extends BaseController } $data = json_decode($request->getContent(), true) ?? []; - if (!empty($data['title'])) $doctor->setName($data['title']); + if (!empty($data['title'])) $doctor->setName(PersianText::stripDoctorTitle($data['title'])); $this->hydrateDoctor($doctor, $data); $this->doctorRepo->save($doctor); diff --git a/src/Representation/Controller/RepresentationActionController.php b/src/Representation/Controller/RepresentationActionController.php index b8848efd..fcf11d61 100644 --- a/src/Representation/Controller/RepresentationActionController.php +++ b/src/Representation/Controller/RepresentationActionController.php @@ -19,6 +19,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\CurrentUser; use Symfony\Component\Security\Http\Attribute\IsGranted; +use App\Shared\Util\PersianText; /** * اکشنهای پنل نماینده: افزودن پزشک/کلینیک، نوبتهای پزشکانِ زیرمجموعه، و پروفایل نمایندهی جاری. @@ -258,7 +259,7 @@ class RepresentationActionController extends BaseController { $data = json_decode($request->getContent(), true) ?? []; $mobile = InputValidator::toEnglishDigits(trim((string) ($data['mobile'] ?? ''))); - $name = trim((string) ($data['name'] ?? '')); + $name = PersianText::stripDoctorTitle((string) ($data['name'] ?? '')); if ($mobile === '' || $name === '') { return $this->error(ErrorCodes::ERR_VALIDATION_002, 'موبایل و نام الزامی هستند', 422); diff --git a/src/Shared/Command/SeedDemoDataCommand.php b/src/Shared/Command/SeedDemoDataCommand.php index 84b50e14..3a6c5d70 100644 --- a/src/Shared/Command/SeedDemoDataCommand.php +++ b/src/Shared/Command/SeedDemoDataCommand.php @@ -333,7 +333,7 @@ class SeedDemoDataCommand extends Command $userRows[] = [ 'uuid' => Uuid::v4()->toRfc4122(), 'mobile_number' => sprintf('09126%06d', $i), - 'real_name' => 'دکتر ' . $this->persianName($i), + 'real_name' => $this->persianName($i), 'roles' => json_encode(['ROLE_USER', 'ROLE_DOCTOR']), 'national_code_verified' => 0, 'status' => 1, @@ -359,7 +359,7 @@ class SeedDemoDataCommand extends Command $doctorRows[] = [ 'uuid' => Uuid::v4()->toRfc4122(), 'user_id' => $userId, - 'name' => 'دکتر ' . $this->persianName($i), + 'name' => $this->persianName($i), 'gender' => $i % 3 === 0 ? 'woman' : 'man', 'medical_system_code' => (string) (100000 + $i), 'mobile_number' => sprintf('09126%06d', $i), diff --git a/tests/Clinic/CreateClinicValidationTest.php b/tests/Clinic/CreateClinicValidationTest.php new file mode 100644 index 00000000..aff6c612 --- /dev/null +++ b/tests/Clinic/CreateClinicValidationTest.php @@ -0,0 +1,69 @@ +createUser(['ROLE_USER']); + + $body = $this->authJson('POST', '/api/v1/clinic', $user); + + $this->assertSame(422, $this->responseCode()); + $this->assertFalse($body['success']); + $this->assertSame('name', $body['errors'][0]['field'] ?? null); + } + + public function testBlankNameIsRejected(): void + { + $user = $this->createUser(['ROLE_USER']); + + $this->authJson('POST', '/api/v1/clinic', $user, ['name' => ' ']); + + $this->assertSame(422, $this->responseCode()); + } + + public function testNamedClinicIsCreatedAndGrantsClinicRole(): void + { + $user = $this->createUser(['ROLE_USER']); + + $this->authJson('POST', '/api/v1/clinic', $user, ['name' => 'کلینیک شفا']); + + $this->assertSame(201, $this->responseCode()); + + $this->em->refresh($user); + $this->assertContains('ROLE_CLINIC', $user->getRoles()); + + $clinic = $this->em->getRepository(Clinic::class)->findOneBy(['user' => $user]); + $this->assertNotNull($clinic); + $this->assertSame('کلینیک شفا', $clinic->getName()); + } + + public function testSecondClinicForTheSameUserIsRejected(): void + { + $user = $this->createUser(['ROLE_USER']); + + $this->authJson('POST', '/api/v1/clinic', $user, ['name' => 'کلینیک اول']); + $this->assertSame(201, $this->responseCode()); + + $body = $this->authJson('POST', '/api/v1/clinic', $user, ['name' => 'کلینیک دوم']); + + $this->assertSame(409, $this->responseCode()); + $this->assertSame('ERR_CONFLICT_001', $body['errors'][0]['code'] ?? null); + + $count = $this->em->getRepository(Clinic::class)->count(['user' => $user]); + $this->assertSame(1, $count, 'a user must never end up owning more than one clinic'); + } +} diff --git a/tests/Doctor/DoctorNameTitleTest.php b/tests/Doctor/DoctorNameTitleTest.php new file mode 100644 index 00000000..42be91a3 --- /dev/null +++ b/tests/Doctor/DoctorNameTitleTest.php @@ -0,0 +1,73 @@ +createUser(['ROLE_ADMIN']); + $mobile = $this->freshMobile(); + + $this->authJson('POST', '/api/v1/admin/doctors', $admin, [ + 'mobile' => $mobile, + 'name' => $name, + ]); + + $this->assertSame(201, $this->responseCode()); + + return $this->em->getRepository(Doctor::class) + ->createQueryBuilder('d') + ->join('d.user', 'u')->where('u.mobileNumber = :m')->setParameter('m', $mobile) + ->getQuery()->getOneOrNullResult(); + } + + public function testAdminCreateStripsTheTitle(): void + { + $doctor = $this->createDoctorAs('دکتر حامد حسینی'); + + $this->assertNotNull($doctor); + $this->assertSame('حامد حسینی', $doctor->getName()); + } + + public function testPlainNameIsLeftAlone(): void + { + $doctor = $this->createDoctorAs('حامد حسینی'); + + $this->assertNotNull($doctor); + $this->assertSame('حامد حسینی', $doctor->getName()); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('titleVariants')] + public function testStripDoctorTitle(string $input, string $expected): void + { + $this->assertSame($expected, PersianText::stripDoctorTitle($input)); + } + + public static function titleVariants(): array + { + return [ + 'plain' => ['حامد حسینی', 'حامد حسینی'], + 'single title' => ['دکتر حامد حسینی', 'حامد حسینی'], + 'repeated title' => ['دکتر دکتر حامد حسینی', 'حامد حسینی'], + 'extra whitespace' => [' دکتر حامد حسینی ', 'حامد حسینی'], + 'title inside' => ['حامد دکتر حسینی', 'حامد دکتر حسینی'], + ]; + } +}