diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 7c889cb..fe4a23a 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -2,6 +2,7 @@ namespace App\Console; +use App\Jobs\AutoCloseOldTickets; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; @@ -23,6 +24,7 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { + $schedule->job(AutoCloseOldTickets::class)->dailyAt('08:00'); } /** diff --git a/app/Events/TicketStatusChanged.php b/app/Events/TicketStatusChanged.php index 804cb3b..fb4c471 100644 --- a/app/Events/TicketStatusChanged.php +++ b/app/Events/TicketStatusChanged.php @@ -3,6 +3,7 @@ namespace App\Events; use App\Models\Ticket; +use App\Models\User; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; @@ -16,16 +17,24 @@ class TicketStatusChanged /** @var Ticket */ public $ticket; + /** @var User */ + public $user; + public $status; + /** @var string */ + public $message; + /** * Create a new event instance. * * @return void */ - public function __construct(Ticket $ticket, $status) + public function __construct(Ticket $ticket, $status, User $user = null, string $message = null) { $this->ticket = $ticket; $this->status = $status; + $this->user = $user; + $this->message = $message; } } diff --git a/app/Jobs/AutoCloseOldTickets.php b/app/Jobs/AutoCloseOldTickets.php new file mode 100644 index 0000000..1279d11 --- /dev/null +++ b/app/Jobs/AutoCloseOldTickets.php @@ -0,0 +1,37 @@ +orWhere('status', TicketStatus::processed()) + ->where('updated_at', '<', Carbon::now()->subDays(15)) + ->whereDoesntHave('messages', function ($q) { + $q->where('updated_at', '<', Carbon::now()->subDays(15)); + })->get(); + foreach ($tickets as $ticket) { + $oldStatus = $ticket->status; + $ticket->status = TicketStatus::archived(); + $ticket->saveQuietly(); + if (!$ticket->subscriptions->contains($ticket->user)) { + event(new TicketStatusChanged($ticket, $oldStatus, null, $message)); + } + event(new NotifySubscriptions($ticket, $message)); + } + } +} diff --git a/app/Listeners/AddStatusMessages.php b/app/Listeners/AddStatusMessages.php index fa71d25..64f3377 100644 --- a/app/Listeners/AddStatusMessages.php +++ b/app/Listeners/AddStatusMessages.php @@ -17,7 +17,14 @@ class AddStatusMessages { $message = new Message(); $message->ticket_id = $event->ticket->id; - $message->message = "Le status est passé à : " . $event->ticket->status->label; + if ($event->message) { + $message->message = $event->message; + } elseif ($event->user) { + // phpcs:ignore Generic.Files.LineLength.TooLong + $message->message = "{$event->user->name} a modifié le statut en {$event->ticket->status->label}"; + } else { + $message->message = "Le statut est passé à : {$event->ticket->status->label}"; + } $message->save(); } } diff --git a/app/Models/File.php b/app/Models/File.php index f62566b..af1a345 100644 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -90,10 +90,18 @@ class File extends Model 'validate' => [ 'required_without:uri', 'nullable', - 'mimes:pdf', - 'max:5120' + 'max:5120', + //phpcs:ignore Generic.Files.LineLength.TooLong + 'mimetypes:application/pdf,text/csv,text/plain,application/csv,text/comma-separated-values,text/anytext,application/octet-stream,application/txt', + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed + function ($attribute, $value, $fail) { + if (!in_array($value->getClientMimeType(), ['application/vnd.ms-excel', 'application/pdf'])) { + //phpcs:ignore Generic.Files.LineLength.TooLong + $fail(__('Le fichier doit être d\'un mime type application/vnd.ms-excel ou application/pdf')); + } + } ], - 'accept' => 'application/pdf', + 'accept' => 'application/pdf,text/csv', 'actions' => [ CrudAction::view(), CrudAction::create() diff --git a/app/Models/Ticket.php b/app/Models/Ticket.php index 314e7a9..2e359d5 100644 --- a/app/Models/Ticket.php +++ b/app/Models/Ticket.php @@ -44,7 +44,7 @@ class Ticket extends Model static::updating(function (Ticket $ticket) { $oldStatus = Ticket::where('id', $ticket->id)->first()->status; if (!$ticket->status->equals($oldStatus)) { - event(new TicketStatusChanged($ticket, $oldStatus)); + event(new TicketStatusChanged($ticket, $oldStatus, auth()->user())); event(new NotifySubscriptions($ticket, sprintf( 'Le ticket à changé de status : %s', $ticket->status->label diff --git a/app/Notifications/ResetPassword.php b/app/Notifications/ResetPassword.php index ecb89e6..351bd5a 100644 --- a/app/Notifications/ResetPassword.php +++ b/app/Notifications/ResetPassword.php @@ -20,9 +20,12 @@ class ResetPassword extends ResetPasswordNotification { $message = (new MailMessage()) ->subject(trans('Reinitialisation de mot de passe')) + //phpcs:ignore Generic.Files.LineLength.TooLong ->line(trans('Vous recevez cet e-mail car nous avons reçu une demande de réinitialisation de mot de passe pour votre compte.')) ->action(trans('Réinitialiser le mot de passe'), $url) + //phpcs:ignore Generic.Files.LineLength.TooLong ->line(trans('Ce lien de réinitialisation de mot de passe expirera dans :count minutes. ', ['count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')])) + //phpcs:ignore Generic.Files.LineLength.TooLong ->line(trans('Si vous n\'avez pas demandé de réinitialisation de mot de passe, aucune autre action n\'est requise.')); $message->viewData['greeting'] = 'Bonjour,'; return $message; diff --git a/app/Policies/TicketPolicy.php b/app/Policies/TicketPolicy.php index 7c81f8c..93b4dbc 100644 --- a/app/Policies/TicketPolicy.php +++ b/app/Policies/TicketPolicy.php @@ -45,16 +45,7 @@ class TicketPolicy extends RootPolicy SSR::make('userSitesHasTicket', $user, $model) ]), 'suscribeToTicket' => TicketStaticRules::make('ticketIsNotArchived', $model), - 'createMessage' => StaticRules::or([ - StaticRules::or([ - URR::make('userIsAdmin', $user), - SSR::make('userSitesHasTicket', $user, $model) - ]), - StaticRules::and([ - TicketStaticRules::make('ticketIsNotArchived', $model), - SSR::make('userSitesHasTicket', $user, $model) - ]) - ]) + 'createMessage' => TicketStaticRules::make('ticketIsNotArchived', $model) ] ]; } diff --git a/composer.json b/composer.json index 311e7a4..699c053 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,8 @@ "laravel-lang/lang": "~8.0", "laravel/framework": "^8.12", "laravel/tinker": "^2.5", - "laravel/ui": "^3.3" + "laravel/ui": "^3.3", + "mews/purifier": "^3.3" }, "require-dev": { "facade/ignition": "^2.5", diff --git a/composer.lock b/composer.lock index 2720737..951be74 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "00fb9cec28ced09a6f6ede716acd4237", + "content-hash": "c6cfdc6c682ed3ffca2b8da4da0b304a", "packages": [ { "name": "asm89/stack-cors", @@ -890,6 +890,60 @@ }, "time": "2019-12-30T22:54:17+00:00" }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.13.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "08e27c97e4c6ed02f37c5b2b20488046c8d90d75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/08e27c97e4c6ed02f37c5b2b20488046c8d90d75", + "reference": "08e27c97e4c6ed02f37c5b2b20488046c8d90d75", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "require-dev": { + "simpletest/simpletest": "dev-master#72de02a7b80c6bb8864ef9bf66d41d2f58f826bd" + }, + "type": "library", + "autoload": { + "psr-0": { + "HTMLPurifier": "library/" + }, + "files": [ + "library/HTMLPurifier.composer.php" + ], + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/master" + }, + "time": "2020-06-29T00:56:53+00:00" + }, { "name": "facade/flare-client-php", "version": "1.8.1", @@ -2230,6 +2284,87 @@ ], "time": "2021-01-18T20:58:21+00:00" }, + { + "name": "mews/purifier", + "version": "3.3.5", + "source": { + "type": "git", + "url": "https://github.com/mewebstudio/Purifier.git", + "reference": "53f2be3b94cf8940d23970f0aeb907ce8c9760f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mewebstudio/Purifier/zipball/53f2be3b94cf8940d23970f0aeb907ce8c9760f7", + "reference": "53f2be3b94cf8940d23970f0aeb907ce8c9760f7", + "shasum": "" + }, + "require": { + "ezyang/htmlpurifier": "4.13.*", + "illuminate/config": "^5.8|^6.0|^7.0|^8.0", + "illuminate/filesystem": "^5.8|^6.0|^7.0|^8.0", + "illuminate/support": "^5.8|^6.0|^7.0|^8.0", + "php": "^7.2|^8.0" + }, + "require-dev": { + "graham-campbell/testbench": "^3.2|^5.5.1", + "mockery/mockery": "^1.3.3", + "phpunit/phpunit": "^8.0|^9.0" + }, + "suggest": { + "laravel/framework": "To test the Laravel bindings", + "laravel/lumen-framework": "To test the Lumen bindings" + }, + "type": "package", + "extra": { + "laravel": { + "providers": [ + "Mews\\Purifier\\PurifierServiceProvider" + ], + "aliases": { + "Purifier": "Mews\\Purifier\\Facades\\Purifier" + } + } + }, + "autoload": { + "psr-4": { + "Mews\\Purifier\\": "src/" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Muharrem ERİN", + "email": "me@mewebstudio.com", + "homepage": "https://github.com/mewebstudio", + "role": "Developer" + } + ], + "description": "Laravel 5/6/7 HtmlPurifier Package", + "homepage": "https://github.com/mewebstudio/purifier", + "keywords": [ + "Purifier", + "htmlpurifier", + "laravel5 HtmlPurifier", + "laravel5 Purifier", + "laravel5 Security", + "laravel6 HtmlPurifier", + "laravel6 Purifier", + "laravel6 Security", + "security", + "xss" + ], + "support": { + "issues": "https://github.com/mewebstudio/Purifier/issues", + "source": "https://github.com/mewebstudio/Purifier/tree/3.3.5" + }, + "time": "2021-05-17T00:17:01+00:00" + }, { "name": "monolog/monolog", "version": "2.3.2", diff --git a/config/purifier.php b/config/purifier.php new file mode 100644 index 0000000..25d357f --- /dev/null +++ b/config/purifier.php @@ -0,0 +1,108 @@ +set('Core.Encoding', $this->config->get('purifier.encoding')); + * $config->set('Cache.SerializerPath', $this->config->get('purifier.cachePath')); + * if ( ! $this->config->get('purifier.finalize')) { + * $config->autoFinalize = false; + * } + * $config->loadArray($this->getConfig()); + * + * You must NOT delete the default settings + * anything in settings should be compacted with params that needed to instance HTMLPurifier_Config. + * + * @link http://htmlpurifier.org/live/configdoc/plain.html + */ + +// phpcs:disable Generic.Files.LineLength.TooLong +return [ + 'encoding' => 'UTF-8', + 'finalize' => true, + 'cachePath' => storage_path('app/purifier'), + 'cacheFileMode' => 0755, + 'settings' => [ + 'default' => [ + 'HTML.Doctype' => 'HTML 4.01 Transitional', + 'HTML.Allowed' => 'h1,h2,h3,h4,h5,h6,div,b,strong,i,em,u,a[href|title],ul,ol,li,p[style],br,span[style],img[width|height|alt|src]', + 'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align', + 'AutoFormat.AutoParagraph' => false, + 'AutoFormat.RemoveEmpty' => false, + 'AutoFormat.RemoveEmpty.RemoveNbsp' => false, + ], + 'test' => [ + 'Attr.EnableID' => 'true', + ], + "youtube" => [ + "HTML.SafeIframe" => 'true', + "URI.SafeIframeRegexp" => "%^(http://|https://|//)(www.youtube.com/embed/|player.vimeo.com/video/)%", + ], + 'custom_definition' => [ + 'id' => 'html5-definitions', + 'rev' => 1, + 'debug' => false, + 'elements' => [ + // http://developers.whatwg.org/sections.html + ['section', 'Block', 'Flow', 'Common'], + ['nav', 'Block', 'Flow', 'Common'], + ['article', 'Block', 'Flow', 'Common'], + ['aside', 'Block', 'Flow', 'Common'], + ['header', 'Block', 'Flow', 'Common'], + ['footer', 'Block', 'Flow', 'Common'], + + // Content model actually excludes several tags, not modelled here + ['address', 'Block', 'Flow', 'Common'], + ['hgroup', 'Block', 'Required: h1 | h2 | h3 | h4 | h5 | h6', 'Common'], + + // http://developers.whatwg.org/grouping-content.html + ['figure', 'Block', 'Optional: (figcaption, Flow) | (Flow, figcaption) | Flow', 'Common'], + ['figcaption', 'Inline', 'Flow', 'Common'], + + // http://developers.whatwg.org/the-video-element.html#the-video-element + ['video', 'Block', 'Optional: (source, Flow) | (Flow, source) | Flow', 'Common', [ + 'src' => 'URI', + 'type' => 'Text', + 'width' => 'Length', + 'height' => 'Length', + 'poster' => 'URI', + 'preload' => 'Enum#auto,metadata,none', + 'controls' => 'Bool', + ]], + ['source', 'Block', 'Flow', 'Common', [ + 'src' => 'URI', + 'type' => 'Text', + ]], + + // http://developers.whatwg.org/text-level-semantics.html + ['s', 'Inline', 'Inline', 'Common'], + ['var', 'Inline', 'Inline', 'Common'], + ['sub', 'Inline', 'Inline', 'Common'], + ['sup', 'Inline', 'Inline', 'Common'], + ['mark', 'Inline', 'Inline', 'Common'], + ['wbr', 'Inline', 'Empty', 'Core'], + + // http://developers.whatwg.org/edits.html + ['ins', 'Block', 'Flow', 'Common', ['cite' => 'URI', 'datetime' => 'CDATA']], + ['del', 'Block', 'Flow', 'Common', ['cite' => 'URI', 'datetime' => 'CDATA']], + ], + 'attributes' => [ + ['iframe', 'allowfullscreen', 'Bool'], + ['table', 'height', 'Text'], + ['td', 'border', 'Text'], + ['th', 'border', 'Text'], + ['tr', 'width', 'Text'], + ['tr', 'height', 'Text'], + ['tr', 'border', 'Text'], + ], + ], + 'custom_attributes' => [ + ['a', 'target', 'Enum#_blank,_self,_target,_top'], + ], + 'custom_elements' => [ + ['u', 'Inline', 'Inline', 'Common'], + ], + ], + +]; diff --git a/resources/views/ticket/show.blade.php b/resources/views/ticket/show.blade.php index 6b562e9..dbeba5e 100644 --- a/resources/views/ticket/show.blade.php +++ b/resources/views/ticket/show.blade.php @@ -96,7 +96,7 @@ @foreach ($messages as $message) @if ($message->user_id === null)