بر اساس آمار گزارششده، میانگین سرعت مطالعهی افراد در اینترنت بین ۲۰۰ تا ۲۵۰ واژه در هر دقیقه است. احتمالاً وبلاگهایی را دیدهاید که در آنها زمان تقریبی مطالعهی هر پست زیر عنوان آن نوشته شده است. از شما میخواهیم این قابلیت را پیادهسازی کنید.
# جزئیات پروژه
تابعی با نام `estimateReadingTime` پیادهسازی کنید که یک رشته از کاربر دریافت کرده و زمان مطالعهی پست را برحسب دقیقه برگرداند. این تابع باید تعداد کلمات موجود در متن را تقسیم بر ۲۰۰ کرده و سقف آن را برگرداند. همچنین، کاراکترهای `.`، `?`، `!`، `,`، `;` و `:` باید نادیده گرفته شوند. کلمات با یک یا چند کاراکتر `' '`، `'\t'` یا `'\n'` از یکدیگر جدا شدهاند. همچنین، تفاوتی برای طول کلمات قائل نمیشویم.
امضای تابع `estimateReadingTime` بهصورت زیر خواهد بود:
```php
function estimateReadingTime(string $text): int
{
// TODO: Implement
}
```
# مثال
```php
<?php
function estimateReadingTime(string $text): int
{
// TODO: Implement
}
function randomText(int $wordsCount): string
{
$words = [];
for ($i = 0; $i < $wordsCount; $i++) {
$words[] = randomWord();
}
$text = implode(" ", $words);
return $text;
}
function randomWord(): string
{
$vowels = ["a","e","i","o","u"];
$consonants = [
"b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "r", "s", "t", "v", "w", "x", "y", "z"
];
$string = "";
$max = intdiv(rand(4, 8), 2);
for ($i = 1; $i <= $max; $i++) {
$string .= $consonants[array_rand($consonants)];
$string .= $vowels[array_rand($vowels)];
}
return $string;
}
echo estimateReadingTime(randomText(200)); // 1
echo "\n";
echo estimateReadingTime(randomText(201)); // 2
echo "\n";
echo estimateReadingTime(randomText(50)); // 1
echo "\n";
echo estimateReadingTime(randomText(1895)); // 10
```
خروجی موردانتظار:
```
1
2
1
10
```
# آنچه باید آپلود کنید
یک فایل *PHP* که تابع `estimateReadingTime` در آن پیادهسازی شده است آپلود کنید.
# قسمت آموزشی
در این قسمت راهنماییهای سوال، به مرور اضافه میشود. مشکلاتتان در راستای حل سوال را میتوانید از بخش ["سوال بپرسید"](https://quera.ir/contest/clarification/20699/) مطرح کنید.
<details class="blue">
<summary>راهنمایی ۱</summary>
یک روش برای پیادهسازی تابع `estimateReadingTime`، حذف علائم نگارشی، جداسازی متن نهایی بر اساس *whitespace* و در نهایت شمارش تعداد کلمات است:
```php
<?php
function estimateReadingTime(string $text): int
{
$text = str_replace([".", "?", "!", ",", ";", ":"], "", $text);
$text = trim($text);
if (empty($text)) {
return 0;
}
$wordsCount = count(preg_split("/\s+/", $text));
// TODO: return ceil of $wordsCount divided by 200
}
```
</details>
<details class="blue">
<summary>راهنمایی ۲</summary>
روش دیگر برای پیادهسازی تابع `estimateReadingTime`، استفاده از تابع `str_word_count` برای محاسبهی تعداد کلمات موجود در متن است:
```php
<?php
function estimateReadingTime(string $text): int
{
return ceil(str_word_count($text) / 200);
}
```
</details>
تخمین زمان مطالعه
بر اساس آمار گزارششده، میانگین سرعت مطالعهی افراد در اینترنت بین ۲۰۰ تا ۲۵۰ واژه در هر دقیقه است. احتمالاً وبلاگهایی را دیدهاید که در آنها زمان تقریبی مطالعهی هر پست زیر عنوان آن نوشته شده است. از شما میخواهیم این قابلیت را پیادهسازی کنید.
تابعی با نام estimateReadingTime پیادهسازی کنید که یک رشته از کاربر دریافت کرده و زمان مطالعهی پست را برحسب دقیقه برگرداند. این تابع باید تعداد کلمات موجود در متن را تقسیم بر ۲۰۰ کرده و سقف آن را برگرداند. همچنین، کاراکترهای .، ?، !، ,، ; و : باید نادیده گرفته شوند. کلمات با یک یا چند کاراکتر ' '، '\t' یا '\n' از یکدیگر جدا شدهاند. همچنین، تفاوتی برای طول کلمات قائل نمیشویم.
امضای تابع estimateReadingTime بهصورت زیر خواهد بود:
در این قسمت راهنماییهای سوال، به مرور اضافه میشود. مشکلاتتان در راستای حل سوال را میتوانید از بخش "سوال بپرسید" مطرح کنید.
راهنمایی ۱
یک روش برای پیادهسازی تابع estimateReadingTime، حذف علائم نگارشی، جداسازی متن نهایی بر اساس whitespace و در نهایت شمارش تعداد کلمات است:
<?phpfunctionestimateReadingTime(string$text):int{$text=str_replace([".","?","!",",",";",":"],"",$text);$text=trim($text);if(empty($text)){return0;}$wordsCount=count(preg_split("/\s+/",$text));// TODO: return ceil of $wordsCount divided by 200}
PHP
راهنمایی ۲
روش دیگر برای پیادهسازی تابع estimateReadingTime، استفاده از تابع str_word_count برای محاسبهی تعداد کلمات موجود در متن است:
+ محدودیت زمان: ۳ ثانیه
+ محدودیت حافظه: ۶۴ مگابایت
---
همانطور که میدانید، این روزها قیمت دلار بسیار پرنوسان است و روزانه تغییر میکند. حسن یک صرافی دارد، اما محاسبهی قیمت برای او سخت است و از شما میخواهد در هر زمان از سال، قیمت دلار را برای او محاسبه کنید.
شما باید یک لیست از تغییرات قیمت دریافت کرده و قیمت را در روزهای خواسته شده به حسن اعلام کنید.
**نکته:** برای دریافت ورودی، میتوانید از تابع `readline` استفاده کنید.
# ورودی
در خط اول ورودی استاندارد (*stdin*)، عدد صحیح $n$ که بیانگر تعداد نوسانهای قیمت است وارد میشود.
$$0 \leq n < 360$$
در $n$ خط بعدی، تاریخ (با فرمت `mm/dd`) و قیمت دلار در تاریخ ذکرشده وارد میشود. برای سادگی، فرض کنید همهی ماهها ۳۰ روزه هستند. همچنین، **تضمین میشود** که تاریخها از یکدیگر متمایز هستند.
**توجه:** قیمت دلار را در تاریخ `01/01` برابر با `14900` در نظر بگیرید.
در خط بعدی، عدد صحیح $m$ که بیانگر تعداد مشتریهای حسن است وارد میشود.
$$0 \leq m \leq 10^5$$
در $m$ خط بعدی، در هر خط یک تاریخ آمده است.
# خروجی
به ازای هر مشتری، قیمت دلار در آن روز را چاپ کنید.
# مثال
## ورودی نمونه
```
4
05/12 22000
06/06 24100
05/20 17000
06/05 23300
4
06/07
05/12
05/21
06/05
```
## خروجی نمونه
```
24100
22000
17000
23300
```
# قسمت آموزشی
در این قسمت راهنماییهای سوال، به مرور اضافه میشود. مشکلاتتان در راستای حل سوال را میتوانید از بخش ["سوال بپرسید"](https://quera.ir/contest/clarification/20699/) مطرح کنید.
<details class="blue">
<summary>راهنمایی ۱</summary>
از آنجایی که تعداد روزهای سال عدد کوچکی است، سادهترین و بهترین روش برای ذخیرهسازی قیمت دلار در روزهای مختلف، استفاده از یک آرایهی انجمنی است:
```php
<?php
$n = readline();
$prices = [
1 => 14900
];
for ($i = 0; $i < $n; $i++) {
[$date, $price] = explode(" ", readline());
$dayNumber = getDayNumberOfDate($date);
$prices[$dayNumber] = $price;
}
// TODO: complete
```
</details>
<details class="blue">
<summary>راهنمایی ۲</summary>
در ادامه، باید قیمت تاریخهایی که در ورودی نیامدهاند را بر اساس سایر تاریخها مشخص کرد. قیمت در تاریخ $d$ برابر است با قیمت در بزرگترین تاریخی که کوچکتر یا مساوی $d$ است.
```php
<?php
function fillEmptyDates(&$prices)
{
$currentDate = 1;
for ($i = 2; $i <= 360; $i++) {
if (!array_key_exists($i, $prices)) {
$prices[$i] = $prices[$currentDate];
} else {
$currentDate = $i;
}
}
}
function getDayNumberOfDate($date)
{
[$m, $d] = explode("/", $date);
$dayNumber = (($m - 1) * 30) + $d;
return $dayNumber;
}
$n = readline();
$prices = [
1 => 14900
];
for ($i = 0; $i < $n; $i++) {
[$date, $price] = explode(" ", readline());
$dayNumber = getDayNumberOfDate($date);
$prices[$dayNumber] = $price;
}
fillEmptyDates($prices);
```
در نهایت، با دریافت تاریخهای مراجعهی مشتریهای حسن، قیمت متناظر در آن روزها را چاپ میکنیم:
```php
$m = readline();
for ($i = 0; $i < $m; $i++) {
$date = readline();
$dayNumber = getDayNumberOfDate($date);
echo $prices[$dayNumber]."\n";
}
```
</details>
صرافی حسن
محدودیت زمان: ۳ ثانیه
محدودیت حافظه: ۶۴ مگابایت
همانطور که میدانید، این روزها قیمت دلار بسیار پرنوسان است و روزانه تغییر میکند. حسن یک صرافی دارد، اما محاسبهی قیمت برای او سخت است و از شما میخواهد در هر زمان از سال، قیمت دلار را برای او محاسبه کنید.
شما باید یک لیست از تغییرات قیمت دریافت کرده و قیمت را در روزهای خواسته شده به حسن اعلام کنید.
نکته: برای دریافت ورودی، میتوانید از تابع readline استفاده کنید.
در خط اول ورودی استاندارد (stdin)، عدد صحیح n که بیانگر تعداد نوسانهای قیمت است وارد میشود.
0≤n<360
در n خط بعدی، تاریخ (با فرمت mm/dd) و قیمت دلار در تاریخ ذکرشده وارد میشود. برای سادگی، فرض کنید همهی ماهها ۳۰ روزه هستند. همچنین، تضمین میشود که تاریخها از یکدیگر متمایز هستند.
توجه: قیمت دلار را در تاریخ 01/01 برابر با 14900 در نظر بگیرید.
در خط بعدی، عدد صحیح m که بیانگر تعداد مشتریهای حسن است وارد میشود.
در ادامه، باید قیمت تاریخهایی که در ورودی نیامدهاند را بر اساس سایر تاریخها مشخص کرد. قیمت در تاریخ d برابر است با قیمت در بزرگترین تاریخی که کوچکتر یا مساوی d است.
محدود کردن دسترسی بر اساس نقش (*Role Based Access Control*) نوعی مدیریت دسترسی است که در آن هر کاربر دارای یک نقش (*Role*) بوده و هر نقش دارای یک مجموعه دسترسی است. در این سؤال قرار است این سیستم را پیادهسازی کنید.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/20699/download_problem_initial_project/67890/) دانلود کنید. ساختار فایلهای این پروژه بهصورت زیر است:
```
rbac_inital
├── app
│ ├── Console
│ │ └── Kernel.php
│ ├── Exceptions
│ │ └── Handler.php
│ ├── Http
│ │ ├── Controllers
│ │ │ ├── Controller.php
│ │ │ └── RBACController.php
│ │ ├── Middleware
│ │ │ ├── Authenticate.php
│ │ │ ├── EncryptCookies.php
│ │ │ ├── PreventRequestsDuringMaintenance.php
│ │ │ ├── RedirectIfAuthenticated.php
│ │ │ ├── TrimStrings.php
│ │ │ ├── TrustHosts.php
│ │ │ ├── TrustProxies.php
│ │ │ └── VerifyCsrfToken.php
│ │ └── Kernel.php
│ ├── Models
│ │ ├── Permission.php
│ │ ├── Role.php
│ │ └── User.php
│ └── Providers
│ ├── AppServiceProvider.php
│ ├── AuthServiceProvider.php
│ ├── BroadcastServiceProvider.php
│ ├── EventServiceProvider.php
│ └── RouteServiceProvider.php
├── bootstrap
│ ├── cache
│ │ ├── packages.php
│ │ └── services.php
│ └── app.php
├── config
│ ├── app.php
│ ├── auth.php
│ ├── broadcasting.php
│ ├── cache.php
│ ├── cors.php
│ ├── database.php
│ ├── filesystems.php
│ ├── hashing.php
│ ├── logging.php
│ ├── mail.php
│ ├── queue.php
│ ├── services.php
│ ├── session.php
│ └── view.php
├── database
│ ├── factories
│ │ └── UserFactory.php
│ ├── migrations
│ │ ├── 2014_10_12_000000_create_users_table.php
│ │ ├── 2014_10_12_100000_create_password_resets_table.php
│ │ ├── 2019_08_19_000000_create_failed_jobs_table.php
│ │ ├── 2020_09_27_092338_create_roles_table.php
│ │ ├── 2020_09_27_092758_create_permissions_table.php
│ │ ├── 2020_09_27_092917_create_permission_role_table.php
│ │ └── 2020_09_27_093704_create_role_user_table.php
│ ├── seeders
│ │ └── DatabaseSeeder.php
│ └── database.db
├── public
│ ├── favicon.ico
│ ├── index.php
│ ├── robots.txt
│ └── web.config
├── resources
│ ├── css
│ │ └── app.css
│ ├── js
│ │ ├── app.js
│ │ └── bootstrap.js
│ ├── lang
│ │ └── en
│ │ ├── auth.php
│ │ ├── pagination.php
│ │ ├── passwords.php
│ │ └── validation.php
│ └── views
│ └── welcome.blade.php
├── routes
│ ├── api.php
│ ├── channels.php
│ ├── console.php
│ └── web.php
├── storage
│ ├── app
│ │ └── public
│ ├── framework
│ │ ├── cache
│ │ │ └── data
│ │ ├── sessions
│ │ ├── testing
│ │ └── views
│ └── logs
│ └── laravel.log
├── tests
│ ├── Feature
│ │ └── RBACSampleTest.php
│ ├── Unit
│ ├── CreatesApplication.php
│ └── TestCase.php
├── README.md
├── artisan
├── composer.json
├── composer.lock
├── package.json
├── phpunit.xml
├── server.php
└── webpack.mix.js
```
<details class="brown">
<summary>راهاندازی پروژه</summary>
**برای اجرای پروژه، باید `php` و `composer` را از قبل نصب کرده باشید.**
+ ابتدا پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
+ دستور `composer install` را در پوشهی اصلی پروژه برای نصب نیازمندیها اجرا کنید.
</details>
## مدلها
مدلها در پروژه اولیه داده شده است. همچنین میتوانید با اجرای دستور `php artisan migrate` مایگریشنها را اجرا کرده و پایگاه داده را به طور کامل داشته باشید. در این پروژه از پایگاه دادهی *SQLite* استفاده شده است. در ادامه، لیست مدلها و توضیحات آمده است:
1. `User`: مدل کاربر که دارای فیلدهای `name`، `email` و `password` است؛ فیلدهای `name` و `password` را میتوانید دلخواه مقدار دهید.
2. `Role`: مدل نقش که دارای فیلد `name` است
3. `Permission`: مدل دسترسی که دارای فیلد ``name`` است
علاوه بر این مدلها، دو جدول `permission_role` و `role_user` نیز وجود دارند که جدولهای رابطه هستند.
شما باید متدهای زیر را در کلاس `RBACController` پیادهسازی کنید:
1. `createRole($name)`: این متد با ورودی گرفتن نام نقش آن را اضافه میکند.
2. `addPermissionToRole($class, $function, $role)`: این متد با گرفتن نام کلاس، نام متد و نام نقش، اجازه دسترسی یک نقش به یک متد از یک کلاس را میدهد.
3. `addUser($email)`: این متد یک کاربر با ایمیل داده شده را ایجاد میکند.
4. `addRoleToUser($role, $email)`: این متد با با دریافت نام نقش و ایمیل کاربر، آن نقش را به کاربر مورد نظر میدهد. هر کاربر میتواند چند نقش داشته باشد.
برنامهی شما به این صورت داوری میشود که کلاسی با ارثبری از کنترلر اصلی لاراول (`App\Http\Controllers\Controller`) به کد شما اضافه میشود. شما باید چهار متد `create`، `update`، `store` و `edit` را در کلاس `Controller` طوری پیادهسازی کنید که بهترتیب چهار دسترسی `create`، `update`، `store` و `edit` برای کلاسی که از `Controller` ارثبری کرده است را بررسی کند. در صورتی که برای کاربر لاگینشده دسترسی موردنظر وجود نداشته باشد، متد موجود در `Controller` باید عدد `403` را برگرداند (`return 403;`). در غیر اینصورت، این متدها نباید مقداری را برگردانند. **تضمین میشود** که هنگام تست این متدها، کاربر در سایت لاگین کرده است (با استفاده از `Auth::user()` میتوانید اطلاعات کاربر را دریافت کنید).
**توجه:** شما تنها مجاز به تغییر فایلهای موجود در پوشهی `/app` هستید.
# آنچه باید آپلود کنید
پس از اعمال تغییرات، کل پروژه به غیر از پوشهی `vendor` را *Zip* کرده و آپلود کنید. نام فایل *Zip* اهمیتی ندارد.
# قسمت آموزشی
در این قسمت راهنماییهای سوال، به مرور اضافه میشود. مشکلاتتان در راستای حل سوال را میتوانید از بخش ["سوال بپرسید"](https://quera.ir/contest/clarification/20699/) مطرح کنید.
<details class="blue">
<summary>راهنمایی ۱</summary>
برای پیادهسازی متد `createRole`، میتوان از متد `create` در مدل `Role` استفاده کرد. متد `addUser` نیز مشابه این متد پیادهسازی میشود:
```php
public function createRole($name)
{
Role::create([
'name' => $name
]);
return response()->json();
}
```
برای پیادهسازی متد `addRoleToUser`، میتوان ابتدا `Role` متناظر با نام ورودی و `User` متناظر با ایمیل ورودی را دریافت کرد و سپس `Role` را به `User` *attach* کرد:
```php
public function addRoleToUser($role, $email)
{
$role = Role::where('name', $role)->first();
$user = User::where('email', $email)->first();
$user->roles()->attach($role);
return response()->json();
}
```
برای پیادهسازی متد `addPermissionToRole`، یک روش برای ذخیرهسازی نام کلاس و نام متد در فیلد `name` دسترسی، جداسازی آنها با استفاده از یک کاراکتر *space* است:
```php
public function addPermissionToRole($class, $function, $role)
{
$permission = Permission::firstOrCreate([
'name' => $class . ' ' . $function
]);
$role = Role::where('name', $role)->first();
$role->permissions()->attach($permission);
return response()->json();
}
```
</details>
<details class="blue">
<summary>راهنمایی ۲</summary>
برای پیادهسازی متد `create` در کلاس `Controller`، باید ابتدا `Role`های کاربر و سپس `Permission`های متناظر با `Role`ها را دریافت کرد. نام کلاسی که این متد را صدا میزند با استفاده از کلاس `ReflectionClass` یا `get_called_class` قابل دریافت است. در صورتی که دسترسی موردنظر وجود نداشته باشد، باید عدد `403` را برگرداند.
برای جلوگیری از مشکل $N+1$، میتوان از `join` استفاده کرد:
```php
public function create()
{
$className = str_replace(__NAMESPACE__ . '\\', '', get_called_class());
$hasPermission = Auth::user()
->roles()
->join('permission_role', 'roles.id', '=', 'permission_role.role_id')
->join('permissions', 'permission_role.permission_id', '=', 'permissions.id')
->where('permissions.name', $className . ' create')
->exists();
if (!$hasPermission) {
return 403;
}
}
```
سایر متدهای کلاس `Controller` نیز مشابه متد `create` پیادهسازی میشوند.
</details>
RBAC
محدود کردن دسترسی بر اساس نقش (Role Based Access Control) نوعی مدیریت دسترسی است که در آن هر کاربر دارای یک نقش (Role) بوده و هر نقش دارای یک مجموعه دسترسی است. در این سؤال قرار است این سیستم را پیادهسازی کنید.
مدلها در پروژه اولیه داده شده است. همچنین میتوانید با اجرای دستور php artisan migrate مایگریشنها را اجرا کرده و پایگاه داده را به طور کامل داشته باشید. در این پروژه از پایگاه دادهی SQLite استفاده شده است. در ادامه، لیست مدلها و توضیحات آمده است:
User: مدل کاربر که دارای فیلدهای name، email و password است؛ فیلدهای name و password را میتوانید دلخواه مقدار دهید.
Role: مدل نقش که دارای فیلد name است
Permission: مدل دسترسی که دارای فیلد name است
علاوه بر این مدلها، دو جدول permission_role و role_user نیز وجود دارند که جدولهای رابطه هستند.
شما باید متدهای زیر را در کلاس RBACController پیادهسازی کنید:
createRole($name): این متد با ورودی گرفتن نام نقش آن را اضافه میکند.
addPermissionToRole($class, $function, $role): این متد با گرفتن نام کلاس، نام متد و نام نقش، اجازه دسترسی یک نقش به یک متد از یک کلاس را میدهد.
addUser($email): این متد یک کاربر با ایمیل داده شده را ایجاد میکند.
addRoleToUser($role, $email): این متد با با دریافت نام نقش و ایمیل کاربر، آن نقش را به کاربر مورد نظر میدهد. هر کاربر میتواند چند نقش داشته باشد.
برنامهی شما به این صورت داوری میشود که کلاسی با ارثبری از کنترلر اصلی لاراول (App\Http\Controllers\Controller) به کد شما اضافه میشود. شما باید چهار متد create، update، store و edit را در کلاس Controller طوری پیادهسازی کنید که بهترتیب چهار دسترسی create، update، store و edit برای کلاسی که از Controller ارثبری کرده است را بررسی کند. در صورتی که برای کاربر لاگینشده دسترسی موردنظر وجود نداشته باشد، متد موجود در Controller باید عدد 403 را برگرداند (return 403;). در غیر اینصورت، این متدها نباید مقداری را برگردانند. تضمین میشود که هنگام تست این متدها، کاربر در سایت لاگین کرده است (با استفاده از Auth::user() میتوانید اطلاعات کاربر را دریافت کنید).
توجه: شما تنها مجاز به تغییر فایلهای موجود در پوشهی /app هستید.
برای پیادهسازی متد addPermissionToRole، یک روش برای ذخیرهسازی نام کلاس و نام متد در فیلد name دسترسی، جداسازی آنها با استفاده از یک کاراکتر space است:
برای پیادهسازی متد create در کلاس Controller، باید ابتدا Roleهای کاربر و سپس Permissionهای متناظر با Roleها را دریافت کرد. نام کلاسی که این متد را صدا میزند با استفاده از کلاس ReflectionClass یا get_called_class قابل دریافت است. در صورتی که دسترسی موردنظر وجود نداشته باشد، باید عدد 403 را برگرداند.
برای جلوگیری از مشکل N+1، میتوان از join استفاده کرد:
سایر متدهای کلاس Controller نیز مشابه متد create پیادهسازی میشوند.
در این سؤال، شما باید یک بلاگ خیلی ساده را پیادهسازی کنید.
# جزئیات
پروژهی اولیه را از [این لینک](/contest/assignments/20699/download_problem_initial_project/67891/) دانلود کنید. ساختار فایلهای این پروژه بهصورت زیر است:
```
simple_blog_initial
├── app
│ ├── Console
│ │ └── Kernel.php
│ ├── Exceptions
│ │ └── Handler.php
│ ├── Http
│ │ ├── Controllers
│ │ │ ├── Controller.php
│ │ │ └── PostController.php
│ │ ├── Middleware
│ │ │ ├── Authenticate.php
│ │ │ ├── EncryptCookies.php
│ │ │ ├── PreventRequestsDuringMaintenance.php
│ │ │ ├── RedirectIfAuthenticated.php
│ │ │ ├── TrimStrings.php
│ │ │ ├── TrustHosts.php
│ │ │ ├── TrustProxies.php
│ │ │ └── VerifyCsrfToken.php
│ │ └── Kernel.php
│ ├── Models
│ │ ├── FavoritePost.php
│ │ ├── Post.php
│ │ └── User.php
│ ├── Override
│ │ ├── Connection.php
│ │ └── QueryBuilder.php
│ └── Providers
│ ├── AppServiceProvider.php
│ ├── AuthServiceProvider.php
│ ├── BroadcastServiceProvider.php
│ ├── DatabaseServiceProvider.php
│ ├── EventServiceProvider.php
│ └── RouteServiceProvider.php
├── bootstrap
│ ├── cache
│ │ ├── packages.php
│ │ └── services.php
│ └── app.php
├── config
│ ├── app.php
│ ├── auth.php
│ ├── broadcasting.php
│ ├── cache.php
│ ├── cors.php
│ ├── database.php
│ ├── filesystems.php
│ ├── hashing.php
│ ├── logging.php
│ ├── mail.php
│ ├── queue.php
│ ├── services.php
│ ├── session.php
│ └── view.php
├── database
│ ├── factories
│ │ └── UserFactory.php
│ ├── migrations
│ │ ├── 2014_10_12_000000_create_users_table.php
│ │ ├── 2014_10_12_100000_create_password_resets_table.php
│ │ ├── 2019_08_19_000000_create_failed_jobs_table.php
│ │ ├── 2020_09_27_124807_create_table_posts.php
│ │ └── 2020_09_27_133130_create_table_favorite_posts.php
│ ├── seeders
│ │ └── DatabaseSeeder.php
│ └── database.sqlite
├── public
│ ├── favicon.ico
│ ├── index.php
│ ├── robots.txt
│ └── web.config
├── resources
│ ├── css
│ │ └── app.css
│ ├── js
│ │ ├── app.js
│ │ └── bootstrap.js
│ ├── lang
│ │ └── en
│ │ ├── auth.php
│ │ ├── pagination.php
│ │ ├── passwords.php
│ │ └── validation.php
│ └── views
│ ├── posts
│ │ ├── create.blade.php
│ │ ├── edit.blade.php
│ │ ├── index.blade.php
│ │ └── show.blade.php
│ └── welcome.blade.php
├── routes
│ ├── api.php
│ ├── channels.php
│ ├── console.php
│ └── web.php
├── storage
│ ├── app
│ │ └── public
│ ├── framework
│ │ ├── cache
│ │ │ └── data
│ │ ├── sessions
│ │ ├── testing
│ │ └── views
│ └── logs
├── tests
│ ├── Feature
│ │ ├── BlogSampleTest.php
│ │ ├── BlogTest.php
│ │ └── OrderByCustomTest.php
│ ├── Unit
│ ├── CreatesApplication.php
│ └── TestCase.php
├── README.md
├── artisan
├── composer.json
├── composer.lock
├── package.json
├── phpunit.xml
├── server.php
└── webpack.mix.js
```
<details class="brown">
<summary>راهاندازی پروژه</summary>
**برای اجرای پروژه، باید `php` و `composer` را از قبل نصب کرده باشید.**
+ ابتدا پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
+ دستور `composer install` را در پوشهی اصلی پروژه برای نصب نیازمندیها اجرا کنید.
</details>
این بلاگ شامل صفحات زیر است:
1. **صفحهی نوشتن یک پست جدید (`/post/create`):** در این صفحه با وارد کردن یک تیتر و متن محتوای یک پست، آن پست ساخته میشود. پس از ساخت پست، کاربر به صفحهی نمایش آن پست هدایت میشود.
2. **صفحهی ویرایش یک پست (`/post/edit/{id}`):** در این صفحه، تیتر و محتوای فعلی پست نمایش داده میشود و قابل ویرایش است. پس از ویرایش، کاربر به صفحهی نمایش آن پست هدایت میشود.
3. **صفحهی نمایش یک پست (`/post/show/{id}`):** در این صفحه، تیتر و محتوای پست نمایش داده میشود.
4. **صفحه لیست تمام پستها (`/posts`):** در این صفحه، تمامی پستها نمایش داده میشوند و با کلیک بر روی تیتر هر پست، کاربر وارد صفحهی نمایش آن پست میشود.
هر پست شامل یک تیتر و یک متن محتوا است.
هر پست پس از نوشته شدن به لیست پستها در صفحه لیست تمام پستها اضافه میشود.
## مدلها
مدلها در پروژهی اولیه داده شده است. همچنین میتوانید با اجرای دستور `php artisan migrate` مایگریشنها را اجرا کنید و پایگاه داده را بهطور کامل داشته باشید. در این پروژه از پایگاه دادهی *SQLite* استفاده شده است. در ادامه، لیست مدلها و توضیحات آمده است:
1. `Post`: مدل پستها که دارای دو فیلد تیتر (`title`) و محتوا (`contents`) است
2. `FavoritePost`: مدل پستهای مورد علاقه که دارای یک فیلد `post_id` است و مشخصکنندهی پستهای موردعلاقه است. با کلیک روی لینک «اضافه به علاقهمندیها»، در صورتی که پست موردنظر از قبل موردعلاقه نباشد، یک ردیف به این جدول اضافه میشود.
شما باید `PostController` و *view*های مربوطه را طوری کامل کنید که عملیات ساخت، ویرایش و نمایش پستها (بهصورت تکی و در لیست) به درستی کار کند.
در صفحهی نمایش یک پست و همچنین صفحه نمایش لیست پستها، برای هر پست دکمهی «اضافه کردن به علاقهمندیها» وجود دارد. در صفحهی لیست پستها، ابتدا پستهایی که به لیست علاقهمندیها اضافه شدهاند بهترتیب زمان اضافه شدن به علاقهمندی نمایش داده میشوند و سپس بقیهی پستها بهترتیب تاریخ ایجاد.
## کنترلرها
متدهای زیر را در کنترلر `PostController` پیادهسازی کنید:
1. `index()`: این متد باید لیست پستها را با ترتیبی که قبلاً ذکر شد با نام `posts` به *view*`posts.index` ارسال کند.
2. `store(Request $request)`: این متد باید یک پست جدید با اطلاعات ورودی (`title` و `contents`) به پایگاه داده اضافه کرده و کاربر را به صفحهی `/post/show/{id}` هدایت کند.
3. `edit($id)`: این متد باید اطلاعات پست با شناسهی `$id` را با نام `post` به *view* `posts.edit` ارسال کند.
4. `update($id, Request $request)`: این متد باید پست با شناسهی `$id` را با اطلاعات ورودی (`title` و `contents`) بهروزرسانی کرده و کاربر را به صفحهی `/post/show/{id}` هدایت کند.
5. `show($id)`: این متد باید اطلاعات پست با شناسهی `$id` را با نام `post` به *view* `posts.show` ارسال کند.
6. `favorite($id)`: این متد باید پست با شناسهی `$id` را به جدول علاقهمندیها اضافه کرده و کاربر را به صفحهی قبلیاش هدایت کند.
برای نمایش پستهای اضافه شده به علاقهمندیها، شما باید در کلاس `\App\Providers\DatabaseServiceProvider` یک *macro* جدید با نام `orderByCustom` به `QueryBuilder` اضافه کنید. این *macro* باید به اینصورت کار کند:
```php
User::orderByCustom('id', 3, 7, 2, 8)->get();
```
کوئری معادل فراخوانی متد بالا:
```sql
select * from "users" order by (case "id" when 3 then 0 when 7 then 1 when 2 then 2 when 8 then 3 else 4 end)
```
همچنین اگر فقط یک آرگومان به این متد داده شود (یا آرگومانهای دوم به بعد خالی باشند)، نباید تغییری در کوئری اعمال شود.
**توجه:** شما تنها مجاز به تغییر فایلهای موجود در پوشهی `/app` هستید.
# آنچه باید آپلود کنید
پس از اعمال تغییرات، کل پروژه به غیر از پوشهی `vendor` را *Zip* کرده و آپلود کنید. نام فایل *Zip* اهمیتی ندارد.
# قسمت آموزشی
در این قسمت راهنماییهای سوال، به مرور اضافه میشود. مشکلاتتان در راستای حل سوال را میتوانید از بخش ["سوال بپرسید"](https://quera.ir/contest/clarification/20699/) مطرح کنید.
<details class="blue">
<summary>راهنمایی ۱</summary>
برای پیادهسازی *macro* `orderByCustom` باید از عملگر `...` استفاده کرد:
```php
Builder::macro('orderByCustom', function ($column, ...$values) {
if (empty($values)) {
return $this;
}
$orderByQuery = '(case "' . $column . '"';
$i = 0;
foreach ($values as $value) {
$orderByQuery .= ' when ' . $value . ' then ' . ($i++);
}
$orderByQuery .= ' else ' . $i . ' end)';
return $this->orderByRaw($orderByQuery);
});
```
</details>
<details class="blue">
<summary>راهنمایی ۲</summary>
در صفحهی لیست پستها، باید ابتدا پستهایی که به علاقهمندیها اضافه شدهاند نمایش داده شوند و سپس سایر پستها. بنابراین، بدنهی متد `index` در کلاس `PostController` بهصورت زیر خواهد بود:
```php
public function index()
{
$favorites = FavoritePost::pluck('post_id')->flatten()->toArray();
$posts = Post::orderByCustom('id', ...$favorites)->orderBy('created_at', 'desc')->get();
return view('posts.index')->with('posts', $posts);
}
```
</details>
بلاگ خیلی ساده
در این سؤال، شما باید یک بلاگ خیلی ساده را پیادهسازی کنید.
برای اجرای پروژه، باید php و composer را از قبل نصب کرده باشید.
ابتدا پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
دستور composer install را در پوشهی اصلی پروژه برای نصب نیازمندیها اجرا کنید.
این بلاگ شامل صفحات زیر است:
صفحهی نوشتن یک پست جدید (/post/create): در این صفحه با وارد کردن یک تیتر و متن محتوای یک پست، آن پست ساخته میشود. پس از ساخت پست، کاربر به صفحهی نمایش آن پست هدایت میشود.
صفحهی ویرایش یک پست (/post/edit/{id}): در این صفحه، تیتر و محتوای فعلی پست نمایش داده میشود و قابل ویرایش است. پس از ویرایش، کاربر به صفحهی نمایش آن پست هدایت میشود.
صفحهی نمایش یک پست (/post/show/{id}): در این صفحه، تیتر و محتوای پست نمایش داده میشود.
صفحه لیست تمام پستها (/posts): در این صفحه، تمامی پستها نمایش داده میشوند و با کلیک بر روی تیتر هر پست، کاربر وارد صفحهی نمایش آن پست میشود.
هر پست شامل یک تیتر و یک متن محتوا است.
هر پست پس از نوشته شدن به لیست پستها در صفحه لیست تمام پستها اضافه میشود.
مدلها در پروژهی اولیه داده شده است. همچنین میتوانید با اجرای دستور php artisan migrate مایگریشنها را اجرا کنید و پایگاه داده را بهطور کامل داشته باشید. در این پروژه از پایگاه دادهی SQLite استفاده شده است. در ادامه، لیست مدلها و توضیحات آمده است:
Post: مدل پستها که دارای دو فیلد تیتر (title) و محتوا (contents) است
FavoritePost: مدل پستهای مورد علاقه که دارای یک فیلد post_id است و مشخصکنندهی پستهای موردعلاقه است. با کلیک روی لینک «اضافه به علاقهمندیها»، در صورتی که پست موردنظر از قبل موردعلاقه نباشد، یک ردیف به این جدول اضافه میشود.
شما باید PostController و viewهای مربوطه را طوری کامل کنید که عملیات ساخت، ویرایش و نمایش پستها (بهصورت تکی و در لیست) به درستی کار کند.
در صفحهی نمایش یک پست و همچنین صفحه نمایش لیست پستها، برای هر پست دکمهی «اضافه کردن به علاقهمندیها» وجود دارد. در صفحهی لیست پستها، ابتدا پستهایی که به لیست علاقهمندیها اضافه شدهاند بهترتیب زمان اضافه شدن به علاقهمندی نمایش داده میشوند و سپس بقیهی پستها بهترتیب تاریخ ایجاد.
متدهای زیر را در کنترلر PostController پیادهسازی کنید:
index(): این متد باید لیست پستها را با ترتیبی که قبلاً ذکر شد با نام posts به viewposts.index ارسال کند.
store(Request $request): این متد باید یک پست جدید با اطلاعات ورودی (title و contents) به پایگاه داده اضافه کرده و کاربر را به صفحهی /post/show/{id} هدایت کند.
edit($id): این متد باید اطلاعات پست با شناسهی $id را با نام post به viewposts.edit ارسال کند.
update($id, Request $request): این متد باید پست با شناسهی $id را با اطلاعات ورودی (title و contents) بهروزرسانی کرده و کاربر را به صفحهی /post/show/{id} هدایت کند.
show($id): این متد باید اطلاعات پست با شناسهی $id را با نام post به viewposts.show ارسال کند.
favorite($id): این متد باید پست با شناسهی $id را به جدول علاقهمندیها اضافه کرده و کاربر را به صفحهی قبلیاش هدایت کند.
برای نمایش پستهای اضافه شده به علاقهمندیها، شما باید در کلاس \App\Providers\DatabaseServiceProvider یک macro جدید با نام orderByCustom به QueryBuilder اضافه کنید. این macro باید به اینصورت کار کند:
در این قسمت راهنماییهای سوال، به مرور اضافه میشود. مشکلاتتان در راستای حل سوال را میتوانید از بخش "سوال بپرسید" مطرح کنید.
راهنمایی ۱
برای پیادهسازی macroorderByCustom باید از عملگر ... استفاده کرد:
Builder::macro('orderByCustom',function($column,...$values){if(empty($values)){return$this;}$orderByQuery='(case "'.$column.'"';$i=0;foreach($valuesas$value){$orderByQuery.=' when '.$value.' then '.($i++);}$orderByQuery.=' else '.$i.' end)';return$this->orderByRaw($orderByQuery);});
PHP
راهنمایی ۲
در صفحهی لیست پستها، باید ابتدا پستهایی که به علاقهمندیها اضافه شدهاند نمایش داده شوند و سپس سایر پستها. بنابراین، بدنهی متد index در کلاس PostController بهصورت زیر خواهد بود:
محمدرضا در شرکت عدالتخانه بهعنوان توسعهدهندهی بکاند مشغول به کار است. او اخیراً با یک چالش جدید مواجه شده است. همانطور که مطلع هستید، در *Laravel 8* قابلیتهای جدیدی به حالت تعمیر (*maintenance mode*) اضافه شده است. یکی از این قابلیتها، امکان تعریف کلید *secret* برای حالت تعمیر است که با استفاده از آن میتوان سایت را خارج از حالت تعمیر مشاهده کرد. این قابلیت در واقع یک کوکی با نام `laravel_maintenance` در مرورگر ذخیره کرده و با استفاده از آن، حالت تعمیر را برای کاربر فعلی غیرفعال میکند.
زمان اعتبار این کوکی ۱۲ ساعت است، اما تیم فنی شرکت عدالتخانه قصد دارد یک آرگومان به دستور `down` موجود در *Artisan* اضافه کند که در صورت مقداردهی آن، کوکی `laravel_maintenance` با زمان اعتبار واردشده در این آرگومان ست شود.
محمدرضا این *task* را برعهده گرفته، اما از پس آن برنیامده است. از شما میخواهیم این *task* را برای او انجام دهید.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/20699/download_problem_initial_project/67892/) دانلود کنید. ساختار فایلهای این پروژه بهصورت زیر است:
```
maintenance_initial
├── app
│ ├── Console
│ │ └── Kernel.php
│ ├── Exceptions
│ │ └── Handler.php
│ ├── Http
│ │ ├── Controllers
│ │ │ └── Controller.php
│ │ ├── Middleware
│ │ │ ├── Authenticate.php
│ │ │ ├── EncryptCookies.php
│ │ │ ├── PreventRequestsDuringMaintenance.php
│ │ │ ├── RedirectIfAuthenticated.php
│ │ │ ├── TrimStrings.php
│ │ │ ├── TrustHosts.php
│ │ │ ├── TrustProxies.php
│ │ │ └── VerifyCsrfToken.php
│ │ └── Kernel.php
│ ├── Models
│ │ └── User.php
│ └── Providers
│ ├── AppServiceProvider.php
│ ├── AuthServiceProvider.php
│ ├── BroadcastServiceProvider.php
│ ├── EventServiceProvider.php
│ └── RouteServiceProvider.php
├── bootstrap
│ ├── cache
│ │ ├── packages.php
│ │ └── services.php
│ └── app.php
├── config
│ ├── app.php
│ ├── auth.php
│ ├── broadcasting.php
│ ├── cache.php
│ ├── cors.php
│ ├── database.php
│ ├── filesystems.php
│ ├── hashing.php
│ ├── logging.php
│ ├── mail.php
│ ├── queue.php
│ ├── services.php
│ ├── session.php
│ └── view.php
├── database
│ ├── factories
│ │ └── UserFactory.php
│ ├── migrations
│ │ ├── 2014_10_12_000000_create_users_table.php
│ │ ├── 2014_10_12_100000_create_password_resets_table.php
│ │ └── 2019_08_19_000000_create_failed_jobs_table.php
│ └── seeders
│ └── DatabaseSeeder.php
├── public
│ ├── favicon.ico
│ ├── index.php
│ ├── robots.txt
│ └── web.config
├── resources
│ ├── css
│ │ └── app.css
│ ├── js
│ │ ├── app.js
│ │ └── bootstrap.js
│ ├── lang
│ │ └── en
│ │ ├── auth.php
│ │ ├── pagination.php
│ │ ├── passwords.php
│ │ └── validation.php
│ └── views
│ └── welcome.blade.php
├── routes
│ ├── api.php
│ ├── channels.php
│ ├── console.php
│ └── web.php
├── storage
│ ├── app
│ │ └── public
│ ├── framework
│ │ ├── cache
│ │ │ └── data
│ │ ├── sessions
│ │ ├── testing
│ │ └── views
│ └── logs
├── tests
│ ├── Feature
│ │ └── ExampleTest.php
│ ├── Unit
│ │ └── ExampleTest.php
│ ├── CreatesApplication.php
│ └── TestCase.php
├── README.md
├── artisan
├── composer.json
├── composer.lock
├── package.json
├── phpunit.xml
├── server.php
└── webpack.mix.js
```
<details class="brown">
<summary>راهاندازی پروژه</summary>
**برای اجرای پروژه، باید `php` و `composer` را از قبل نصب کرده باشید.**
+ ابتدا پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
+ دستور `composer install` را در پوشهی اصلی پروژه برای نصب نیازمندیها اجرا کنید.
</details>
آرگومانی با نام `time` و مقدار پیشفرض `12` به دستور `down` در *Artisan* اضافه کنید که در صورت مقداردهی شدن، زمان کوکی `laravel-maintenance` برابر با مقدار آرگومان `time` باشد. مقدار آرگومان `time` یک عدد صحیح بوده و بیانگر زمان کوکی `laravel-maintenance` برحسب ساعت است. **تضمین میشود** که عدد صفر و اعداد منفی به این آرگومان داده نمیشوند.
دستور `down` بهصورت زیر اجرا خواهد شد:
```
php artisan down --secret=MvYgEH651d3X4JRcys --time=24
```
در اینصورت، کاربر با ارسال درخواست به آدرس `/MvYgEH651d3X4JRcys` میتواند سایت را به مدت ۲۴ ساعت خارج از حالت تعمیر مشاهده کند.
# نکات
+ برای پیادهسازی این قابلیت، نیازمند جستوجو در سورسکد *Laravel* خواهید بود.
+ شما تنها مجاز به ایجاد تغییرات در پوشههای `app` و `config` هستید.
# آنچه باید آپلود کنید
پس از اعمال تغییرات، کل پروژه به غیر از پوشهی `vendor` را *Zip* کرده و آپلود کنید. نام فایل *Zip* اهمیتی ندارد.
# قسمت آموزشی
در این قسمت راهنماییهای سوال، به مرور اضافه میشود. مشکلاتتان در راستای حل سوال را میتوانید از بخش ["سوال بپرسید"](https://quera.ir/contest/clarification/20699/) مطرح کنید.
<details class="blue">
<summary>راهنمایی ۱</summary>
برای تغییر رفتار دستور `down`، باید کلاس جدیدی نظیر `App\Console\Commands\ExtendedDownCommand` تعریف کرد. این کلاس میتواند از کلاس `Illuminate\Foundation\Console\DownCommand` ارثبری کند. تنها تفاوت این کلاس با کلاس `DownCommand` در *signature* و بدنهی متد `getDownFilePayload` است.
```php
class ExtendedDownCommand extends DownCommand
{
protected $signature = 'down {--redirect= : The path that users should be redirected to}
{--render= : The view that should be prerendered for display during maintenance mode}
{--retry= : The number of seconds after which the request may be retried}
{--secret= : The secret phrase that may be used to bypass maintenance mode}
{--time=12 : Bypass cookie expiration time (hours)}
{--status=503 : The status code that should be used when returning the maintenance mode response}';
protected function getDownFilePayload()
{
return [
'redirect' => $this->redirectPath(),
'retry' => $this->getRetryTime(),
'secret' => $this->option('secret'),
'time' => (int) $this->option('time'),
'status' => (int) $this->option('status', 503),
'template' => $this->option('render') ? $this->prerenderView() : null,
];
}
}
```
برای پیادهسازی متدی جهت ساخت کوکی *bypass*، میتوان کلاسی مشابه کلاس `Illuminate\Foundation\Http\MaintenanceModeBypassCookie` تعریف کرد، با این تفاوت که متد `create` آن یک آرگومان جدید با نام `time` نیز میپذیرد:
```php
<?php
namespace App\Http;
use Illuminate\Support\Carbon;
use Symfony\Component\HttpFoundation\Cookie;
class MaintenanceModeBypassCookie
{
public static function create(string $key, int $time = 12)
{
if ($time < 1) {
$time = 1;
}
$expiresAt = Carbon::now()->addHours($time);
return new Cookie('laravel_maintenance', base64_encode(json_encode([
'expires_at' => $expiresAt->getTimestamp(),
'mac' => hash_hmac('SHA256', $expiresAt->getTimestamp(), $key),
])), $expiresAt);
}
public static function isValid(string $cookie, string $key)
{
$payload = json_decode(base64_decode($cookie), true);
return is_array($payload) &&
is_numeric($payload['expires_at'] ?? null) &&
isset($payload['mac']) &&
hash_equals(hash_hmac('SHA256', $payload['expires_at'], $key), $payload['mac']) &&
(int) $payload['expires_at'] >= Carbon::now()->getTimestamp();
}
}
```
</details>
<details class="blue">
<summary>راهنمایی ۲</summary>
برای تغییر رفتار *secret bypass route* باید یک *middleware* مشابه `Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance` تعریف کرد و بخش ساخت کوکی آن را تغییر داد:
```php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Foundation\Application;
use \App\Http\MaintenanceModeBypassCookie;
use Symfony\Component\HttpKernel\Exception\HttpException;
class PreventRequestsDuringMaintenance
{
protected $app;
protected $except = [];
public function __construct(Application $app)
{
$this->app = $app;
}
public function handle($request, Closure $next)
{
if ($this->app->isDownForMaintenance()) {
$data = json_decode(file_get_contents($this->app->storagePath().'/framework/down'), true);
if (isset($data['secret']) && isset($data['time']) && $request->path() === $data['secret']) {
return $this->bypassResponse($data['secret'], $data['time']);
}
if ($this->hasValidBypassCookie($request, $data) ||
$this->inExceptArray($request)) {
return $next($request);
}
if (isset($data['redirect'])) {
$path = $data['redirect'] === '/'
? $data['redirect']
: trim($data['redirect'], '/');
if ($request->path() !== $path) {
return redirect($path);
}
}
if (isset($data['template'])) {
return response(
$data['template'],
$data['status'] ?? 503,
isset($data['retry']) ? ['Retry-After' => $data['retry']] : []
);
}
throw new HttpException(
$data['status'] ?? 503,
'Service Unavailable',
null,
isset($data['retry']) ? ['Retry-After' => $data['retry']] : []
);
}
return $next($request);
}
protected function hasValidBypassCookie($request, array $data)
{
return isset($data['secret']) &&
$request->cookie('laravel_maintenance') &&
MaintenanceModeBypassCookie::isValid(
$request->cookie('laravel_maintenance'),
$data['secret']
);
}
protected function inExceptArray($request)
{
foreach ($this->except as $except) {
if ($except !== '/') {
$except = trim($except, '/');
}
if ($request->fullUrlIs($except) || $request->is($except)) {
return true;
}
}
return false;
}
protected function bypassResponse(string $secret, int $time)
{
return redirect('/')->withCookie(
MaintenanceModeBypassCookie::create($secret, $time)
);
}
}
```
در نهایت، باید یک *service provider* برای *override* کردن دستور پیشفرض `down` ایجاد کرد. بدنهی متد `register` این کلاس بهصورت زیر خواهد بود:
```php
public function register()
{
$this->commands([
ExtendedDownCommand::class
]);
}
```
با افزودن این *service provider* به فایل `app/config.php`، رفتار دستور پیشفرض `down` تغییر میکند.
</details>
حالت تعمیر سفارشی
محمدرضا در شرکت عدالتخانه بهعنوان توسعهدهندهی بکاند مشغول به کار است. او اخیراً با یک چالش جدید مواجه شده است. همانطور که مطلع هستید، در Laravel 8 قابلیتهای جدیدی به حالت تعمیر (maintenance mode) اضافه شده است. یکی از این قابلیتها، امکان تعریف کلید secret برای حالت تعمیر است که با استفاده از آن میتوان سایت را خارج از حالت تعمیر مشاهده کرد. این قابلیت در واقع یک کوکی با نام laravel_maintenance در مرورگر ذخیره کرده و با استفاده از آن، حالت تعمیر را برای کاربر فعلی غیرفعال میکند.
زمان اعتبار این کوکی ۱۲ ساعت است، اما تیم فنی شرکت عدالتخانه قصد دارد یک آرگومان به دستور down موجود در Artisan اضافه کند که در صورت مقداردهی آن، کوکی laravel_maintenance با زمان اعتبار واردشده در این آرگومان ست شود.
محمدرضا این task را برعهده گرفته، اما از پس آن برنیامده است. از شما میخواهیم این task را برای او انجام دهید.
برای اجرای پروژه، باید php و composer را از قبل نصب کرده باشید.
ابتدا پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
دستور composer install را در پوشهی اصلی پروژه برای نصب نیازمندیها اجرا کنید.
آرگومانی با نام time و مقدار پیشفرض 12 به دستور down در Artisan اضافه کنید که در صورت مقداردهی شدن، زمان کوکی laravel-maintenance برابر با مقدار آرگومان time باشد. مقدار آرگومان time یک عدد صحیح بوده و بیانگر زمان کوکی laravel-maintenance برحسب ساعت است. تضمین میشود که عدد صفر و اعداد منفی به این آرگومان داده نمیشوند.
دستور down بهصورت زیر اجرا خواهد شد:
php artisan down --secret=MvYgEH651d3X4JRcys --time=24
Plain text
در اینصورت، کاربر با ارسال درخواست به آدرس /MvYgEH651d3X4JRcys میتواند سایت را به مدت ۲۴ ساعت خارج از حالت تعمیر مشاهده کند.
در این قسمت راهنماییهای سوال، به مرور اضافه میشود. مشکلاتتان در راستای حل سوال را میتوانید از بخش "سوال بپرسید" مطرح کنید.
راهنمایی ۱
برای تغییر رفتار دستور down، باید کلاس جدیدی نظیر App\Console\Commands\ExtendedDownCommand تعریف کرد. این کلاس میتواند از کلاس Illuminate\Foundation\Console\DownCommand ارثبری کند. تنها تفاوت این کلاس با کلاس DownCommand در signature و بدنهی متد getDownFilePayload است.
classExtendedDownCommandextendsDownCommand{protected$signature='down {--redirect= : The path that users should be redirected to}
{--render= : The view that should be prerendered for display during maintenance mode}
{--retry= : The number of seconds after which the request may be retried}
{--secret= : The secret phrase that may be used to bypass maintenance mode}
{--time=12 : Bypass cookie expiration time (hours)}
{--status=503 : The status code that should be used when returning the maintenance mode response}';protectedfunctiongetDownFilePayload(){return['redirect'=>$this->redirectPath(),'retry'=>$this->getRetryTime(),'secret'=>$this->option('secret'),'time'=>(int)$this->option('time'),'status'=>(int)$this->option('status',503),'template'=>$this->option('render')?$this->prerenderView():null,];}}
PHP
برای پیادهسازی متدی جهت ساخت کوکی bypass، میتوان کلاسی مشابه کلاس Illuminate\Foundation\Http\MaintenanceModeBypassCookie تعریف کرد، با این تفاوت که متد create آن یک آرگومان جدید با نام time نیز میپذیرد:
برای تغییر رفتار secret bypass route باید یک middleware مشابه Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance تعریف کرد و بخش ساخت کوکی آن را تغییر داد: