7 |
--------------------------------------------------------------------------------
/src/Contracts/Renderer.php:
--------------------------------------------------------------------------------
1 | new()->message($message)->types(...$types);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/RendererManager.php:
--------------------------------------------------------------------------------
1 | config->get('laralerts.default');
22 | }
23 |
24 | /**
25 | * Creates a Bootstrap renderer.
26 | *
27 | * @return \DarkGhostHunter\Laralerts\Contracts\Renderer
28 | * @throws \Illuminate\Contracts\Container\BindingResolutionException
29 | */
30 | protected function createBootstrapDriver(): Contracts\Renderer
31 | {
32 | return new Renderers\BootstrapRenderer($this->container->make(Factory::class));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Italo Israel Baeza Cabrera
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/src/Http/Middleware/AddAlertsToJson.php:
--------------------------------------------------------------------------------
1 | isSuccessful()) {
30 | $key ??= config('laralerts.key');
31 |
32 | $data = $response->getData();
33 |
34 | $response->setData(
35 | data_set($data, $key, app(Bag::class)->collect()->toArray(), false)
36 | );
37 | }
38 |
39 | return $response;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/View/Component/LaralertsComponent.php:
--------------------------------------------------------------------------------
1 | tags = (array) $tags ?: $bag->getDefaultTags();
26 | }
27 |
28 | /**
29 | * Get the view / view contents that represent the component.
30 | *
31 | * @return string
32 | */
33 | public function render(): string
34 | {
35 | return $this->renderer->render(
36 | $this->bag->collect()->filter->hasAnyTag(...$this->tags)
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/config/laralerts.php:
--------------------------------------------------------------------------------
1 | 'bootstrap',
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Session key
21 | |--------------------------------------------------------------------------
22 | |
23 | | For the Alerts to work, the bag containing them is registered inside the
24 | | Session store by an identifiable key. You may want to change this key
25 | | for any other in case it collides with a key you're already using.
26 | |
27 | */
28 |
29 | 'key' => '_alerts',
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Default tags
34 | |--------------------------------------------------------------------------
35 | |
36 | | Alerts support tagging, meanining you can filter which alerts to present
37 | | in your frontend by a name, like "global" or "admin". This contains the
38 | | default tags all Alerts made in your application will have by default.
39 | |
40 | | Supported: "array", "string".
41 | |
42 | */
43 |
44 | 'tags' => 'default',
45 | ];
46 |
--------------------------------------------------------------------------------
/src/Renderers/BootstrapRenderer.php:
--------------------------------------------------------------------------------
1 | 'alert-primary',
27 | 'secondary' => 'alert-secondary',
28 | 'success' => 'alert-success',
29 | 'danger' => 'alert-danger',
30 | 'warning' => 'alert-warning',
31 | 'info' => 'alert-info',
32 | 'light' => 'alert-light',
33 | 'dark' => 'alert-dark',
34 | ];
35 |
36 | /**
37 | * Classes that should be added when dismissing the alert.
38 | *
39 | * @var array|string[]
40 | */
41 | protected const DISMISS_CLASSES = ['fade', 'show', 'alert-dismissible'];
42 |
43 | /**
44 | * Bootstrap Renderer constructor.
45 | *
46 | * @param \Illuminate\Contracts\View\Factory $factory
47 | */
48 | public function __construct(protected Factory $factory)
49 | {
50 | //
51 | }
52 |
53 | /**
54 | * Returns the rendered alerts as a single HTML string.
55 | *
56 | * @param \Illuminate\Support\Collection|\DarkGhostHunter\Laralerts\Alert[] $alerts
57 | * @return string
58 | */
59 | public function render(Collection $alerts): string
60 | {
61 | return $this->factory
62 | ->make(static::VIEW)
63 | ->with('alerts', $alerts->map([$this, 'compileAlert']))->render();
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "darkghosthunter/laralerts",
3 | "description": "Quickly create one or multiple Alerts from your backend",
4 | "keywords": [
5 | "darkghosthunter",
6 | "laralerts",
7 | "alerts",
8 | "notifications",
9 | "flash",
10 | "session",
11 | "laravel"
12 | ],
13 | "homepage": "https://github.com/darkghosthunter/laralerts",
14 | "license": "MIT",
15 | "type": "library",
16 | "minimum-stability": "dev",
17 | "prefer-stable": true,
18 | "authors": [
19 | {
20 | "name": "Italo Israel Baeza Cabrera",
21 | "email": "darkghosthunter@gmail.com",
22 | "role": "Developer"
23 | }
24 | ],
25 | "require": {
26 | "php": "^8.0||^8.1",
27 | "ext-json": "*",
28 | "illuminate/config": "^8.0",
29 | "illuminate/http": "^8.0",
30 | "illuminate/support": "^8.0",
31 | "illuminate/collections": "^8.0",
32 | "illuminate/session": "^8.0",
33 | "illuminate/view": "^8.0",
34 | "illuminate/routing": "^8.0"
35 | },
36 | "require-dev": {
37 | "orchestra/testbench": "^6.17",
38 | "mockery/mockery": "^1.4.2",
39 | "phpunit/phpunit": "^9.5.4"
40 | },
41 | "autoload": {
42 | "psr-4": {
43 | "DarkGhostHunter\\Laralerts\\": "src"
44 | },
45 | "files": [
46 | "src/helpers.php"
47 | ]
48 | },
49 | "autoload-dev": {
50 | "psr-4": {
51 | "Tests\\": "tests"
52 | }
53 | },
54 | "scripts": {
55 | "test": "vendor/bin/phpunit --coverage-clover build/logs/clover.xml",
56 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage"
57 | },
58 | "config": {
59 | "sort-packages": true
60 | },
61 | "extra": {
62 | "laravel": {
63 | "providers": [
64 | "DarkGhostHunter\\Laralerts\\LaralertsServiceProvider"
65 | ],
66 | "aliases": {
67 | "Alert": "DarkGhostHunter\\Laralerts\\Facades\\Alert"
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Facades/Alert.php:
--------------------------------------------------------------------------------
1 | collect()
14 | * @method static \DarkGhostHunter\Laralerts\Alert persistAs(string $key)
15 | * @method static bool abandon(string $key)
16 | * @method static bool hasPersistent(string $key)
17 | * @method static void flush()
18 | * @method static \DarkGhostHunter\Laralerts\Alert message(string $message)
19 | * @method static \DarkGhostHunter\Laralerts\Alert raw(string $message)
20 | * @method static \DarkGhostHunter\Laralerts\Alert types(string ...$types)
21 | * @method static \DarkGhostHunter\Laralerts\Alert dismiss(bool $dismissible = true)
22 | * @method static \DarkGhostHunter\Laralerts\Alert when(Closure|bool $condition)
23 | * @method static \DarkGhostHunter\Laralerts\Alert unless(Closure|bool $condition)
24 | * @method static \DarkGhostHunter\Laralerts\Alert away(string $replace, string $url, bool $blank = true)
25 | * @method static \DarkGhostHunter\Laralerts\Alert to(string $replace, string $url, bool $blank = false)
26 | * @method static \DarkGhostHunter\Laralerts\Alert route(string $replace, string $name, array $parameters = [], bool $blank = false)
27 | * @method static \DarkGhostHunter\Laralerts\Alert action(string $replace, string|array $action, array $parameters = [], bool $blank = false)
28 | * @method static \DarkGhostHunter\Laralerts\Alert tags(string ...$tags)
29 | * @method static \DarkGhostHunter\Laralerts\Alert fromJson(string $alert, int $options = 0)
30 | */
31 | class Alert extends Facade
32 | {
33 | /**
34 | * Get the registered name of the component.
35 | *
36 | * @return string
37 | */
38 | protected static function getFacadeAccessor(): string
39 | {
40 | return Bag::class;
41 | }
42 |
43 | /**
44 | * Creates a fake Alert Bag.
45 | *
46 | * @return \DarkGhostHunter\Laralerts\Testing\Fakes\BagFake
47 | */
48 | public static function fake(): BagFake
49 | {
50 | $fake = static::$app->make(BagFake::class, [
51 | 'tags' => Arr::wrap(Config::get('laralerts.tags'))
52 | ]);
53 |
54 | static::swap($fake);
55 |
56 | return $fake;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Renderers/CompilesAlert.php:
--------------------------------------------------------------------------------
1 | static::compileMessage($alert),
29 | 'classes' => static::compileClasses($alert),
30 | 'dismissible' => $alert->isDismissible(),
31 | ];
32 | }
33 |
34 | /**
35 | * Parses the message, replacing each keyword by an HTML link.
36 | *
37 | * @param \DarkGhostHunter\Laralerts\Alert $alert
38 | * @return string
39 | */
40 | protected static function compileMessage(Alert $alert): string
41 | {
42 | return str_replace(
43 | array_map(
44 | static function (string $replace): string {
45 | return '{' . $replace . '}';
46 | }, array_column($alert->getLinks(), 'replace')
47 | ),
48 | array_map(static function (object $link): string {
49 | return "url\"".($link->blank ? ' target="_blank"' : '').">$link->replace";
50 | }, $alert->getLinks()),
51 | $alert->getMessage()
52 | );
53 | }
54 |
55 | /**
56 | * Returns a list of classes as a string for a "classes" HTML tag.
57 | *
58 | * @param \DarkGhostHunter\Laralerts\Alert $alert
59 | * @return string
60 | */
61 | protected static function compileClasses(Alert $alert): string
62 | {
63 | $classes = [];
64 |
65 | foreach ($alert->getTypes() as $type) {
66 | if (defined('static::TYPE_CLASSES')) {
67 | array_push($classes, ...Arr::wrap(static::TYPE_CLASSES[$type] ?? $type));
68 | } else {
69 | $classes[] = $type;
70 | }
71 | }
72 |
73 | if (defined('static::DISMISS_CLASSES') && $alert->isDismissible()) {
74 | $classes = array_merge($classes, static::DISMISS_CLASSES);
75 | }
76 |
77 | return implode(' ', array_unique($classes));
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/LaralertsServiceProvider.php:
--------------------------------------------------------------------------------
1 | mergeConfigFrom(__DIR__ . '/../config/laralerts.php', 'laralerts');
21 |
22 | $this->app->singleton(RendererManager::class);
23 | $this->app->singleton(Contracts\Renderer::class, static function (Container $app): Contracts\Renderer {
24 | return $app->make(RendererManager::class)->driver($app->make('config')->get('laralerts.renderer'));
25 | });
26 |
27 | $this->app->singleton(Bag::class, static function (Container $app): Bag {
28 | return new Bag((array) $app->make('config')->get('laralerts.tags', ['default']));
29 | });
30 |
31 | $this->app->bind(
32 | Http\Middleware\StoreAlertsInSession::class,
33 | static function (Container $app): Http\Middleware\StoreAlertsInSession {
34 | return new Http\Middleware\StoreAlertsInSession(
35 | $app->make(Bag::class),
36 | $app->make('session.store'),
37 | $app->make('config')->get('laralerts.key')
38 | );
39 | }
40 | );
41 | }
42 |
43 | /**
44 | * Bootstrap any application services.
45 | *
46 | * @param \Illuminate\Foundation\Http\Kernel $http
47 | * @param \Illuminate\Routing\Router $router
48 | *
49 | * @return void
50 | */
51 | public function boot(Kernel $http, Router $router): void
52 | {
53 | // Add the Global Middleware to the `web` group only if it exists.
54 | if (array_key_exists('web', $http->getMiddlewareGroups())) {
55 | $http->appendMiddlewareToGroup('web', Http\Middleware\StoreAlertsInSession::class);
56 | }
57 |
58 | $router->aliasMiddleware('laralerts.json', Http\Middleware\AddAlertsToJson::class);
59 |
60 | $this->loadViewsFrom(__DIR__ . '/../resources/views', 'laralerts');
61 |
62 | $this->callAfterResolving('blade.compiler', static function (BladeCompiler $blade): void {
63 | $blade->component(View\Component\LaralertsComponent::class, 'laralerts');
64 | });
65 |
66 | if ($this->app->runningInConsole()) {
67 | $this->publishes([__DIR__ . '/../resources/views' => resource_path('views/vendor/laralerts')], 'views');
68 | $this->publishes([__DIR__ . '/../config/laralerts.php' => config_path('laralerts.php')], 'config');
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Testing/Fakes/BagFake.php:
--------------------------------------------------------------------------------
1 | added = $this->alerts;
26 | }
27 |
28 | /**
29 | * Finds an alert by a given key.
30 | *
31 | * @return \DarkGhostHunter\Laralerts\Testing\Builder
32 | */
33 | public function assertAlert(): Builder
34 | {
35 | return new Builder($this);
36 | }
37 |
38 | /**
39 | * Assert that the alert bag has no alerts.
40 | *
41 | * @return void
42 | */
43 | public function assertEmpty(): void
44 | {
45 | $this->assertAlert()->missing("Failed to assert that there is no alerts.");
46 | }
47 |
48 | /**
49 | * Assert that the alert bag has any alert.
50 | *
51 | * @return void
52 | */
53 | public function assertNotEmpty(): void
54 | {
55 | $this->assertAlert()->exists("Failed to assert that there is any alert.");
56 | }
57 |
58 | /**
59 | * Assert the alert bag contains exactly one alert.
60 | *
61 | * @return void
62 | */
63 | public function assertHasOne(): void
64 | {
65 | $this->assertAlert()->unique();
66 | }
67 |
68 | /**
69 | * Assert the alert bag contains exactly the given number of alerts.
70 | *
71 | * @param int $count
72 | * @return void
73 | */
74 | public function assertHas(int $count): void
75 | {
76 | $this->assertAlert()->count($count);
77 | }
78 |
79 | /**
80 | * Assert the alert bag contains an alert persisted by the given key.
81 | *
82 | * @param string $key
83 | * @return void
84 | */
85 | public function assertPersisted(string $key): void
86 | {
87 | $this->assertAlert()->persistedAs($key);
88 | }
89 |
90 | /**
91 | * Assert the alert bag contains persistent alerts.
92 | *
93 | * @return void
94 | */
95 | public function assertHasPersistent(): void
96 | {
97 | $this->assertAlert()->persisted()->exists("Failed to assert that there is any persistent alert.");
98 | }
99 |
100 | /**
101 | * Assert the alert bag doesn't contain persistent alerts.
102 | *
103 | * @return void
104 | */
105 | public function assertHasNoPersistent(): void
106 | {
107 | $this->assertAlert()->persisted()->missing("Failed to assert that there is no persistent alerts.");
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/Http/Middleware/StoreAlertsInSession.php:
--------------------------------------------------------------------------------
1 | getSession()?->isStarted()) {
42 | return $next($request);
43 | }
44 |
45 | $this->sessionAlertsToBag();
46 |
47 | $response = $next($request);
48 |
49 | $this->bagAlertsToSession($response instanceof RedirectResponse);
50 |
51 | return $response;
52 | }
53 |
54 | /**
55 | * Takes the existing alerts in the session and adds them to the bag.
56 | *
57 | * @return void
58 | */
59 | protected function sessionAlertsToBag(): void
60 | {
61 | // Retrieve both persistent and non-persistent alerts and add them.
62 | app(Bag::class)->add(array_merge(
63 | $this->session->get("$this->key.persistent", []),
64 | $this->session->get("$this->key.alerts", []),
65 | ));
66 |
67 | // Removing the alerts from the session ensures these don't duplicate.
68 | $this->session->forget($this->key);
69 | }
70 |
71 | /**
72 | * Move the alerts back to the session.
73 | *
74 | * @param bool $flashIfRedirect
75 | * @return void
76 | */
77 | protected function bagAlertsToSession(bool $flashIfRedirect): void
78 | {
79 | [$persistent, $nonPersistent] = $this->bag->collect()
80 | ->partition(function (Alert $alert): bool {
81 | return in_array($alert->index, $this->bag->getPersisted(), true);
82 | });
83 |
84 | // Persistent keys will be put persistently into the session.
85 | if ($persistent->isNotEmpty()) {
86 | $this->session->put("$this->key.persistent", $persistent->all());
87 | }
88 |
89 | // Non-persistent will be flashed if the response is as redirection.
90 | // This way we allow the next response from the app to have these
91 | // alerts without having to manually flash them from the app.
92 | if ($flashIfRedirect && $nonPersistent->isNotEmpty()) {
93 | $this->session->flash("$this->key.alerts", $nonPersistent->all());
94 | }
95 |
96 | $this->bag->flush();
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/BogusAlert.php:
--------------------------------------------------------------------------------
1 | alerts = new Collection;
39 | }
40 |
41 | /**
42 | * Returns all a key-index map of all persisted alerts.
43 | *
44 | * @return array
45 | */
46 | public function getPersisted(): array
47 | {
48 | return $this->persisted;
49 | }
50 |
51 | /**
52 | * Returns the default list of tags injected in each Alert.
53 | *
54 | * @return array
55 | */
56 | public function getDefaultTags(): array
57 | {
58 | return $this->tags;
59 | }
60 |
61 | /**
62 | * Creates a new Alert into this Bag instance.
63 | *
64 | * @return \DarkGhostHunter\Laralerts\Alert
65 | */
66 | public function new(): Alert
67 | {
68 | $this->add($alert = new Alert(bag: $this, tags: $this->tags));
69 |
70 | return $alert;
71 | }
72 |
73 | /**
74 | * Adds an Alert into the bag.
75 | *
76 | * @param \DarkGhostHunter\Laralerts\Alert|\DarkGhostHunter\Laralerts\Alert[] $alert
77 | * @return \DarkGhostHunter\Laralerts\Bag
78 | */
79 | public function add(Alert|array $alert): static
80 | {
81 | foreach (Arr::wrap($alert) as $item) {
82 | $this->alerts->push($item);
83 |
84 | $item->index = array_key_last($this->alerts->all());
85 |
86 | // The method is also used to put alerts from the session. Because
87 | // of that, we will check if it already has a persistent key and,
88 | // if it has one, we will add it to the internal map of alerts.
89 | if ($key = $item->getPersistKey()) {
90 | $this->persisted[$key] = $item->index;
91 | }
92 |
93 | $item->setBag($this);
94 | }
95 |
96 | return $this;
97 | }
98 |
99 | /**
100 | * Returns the underlying collection of alerts.
101 | *
102 | * @return \Illuminate\Support\Collection|\DarkGhostHunter\Laralerts\Alert[]
103 | */
104 | public function collect(): Collection
105 | {
106 | return $this->alerts;
107 | }
108 |
109 | /**
110 | * Marks an existing Alert as persistent.
111 | *
112 | * @param string $key
113 | * @param int $index
114 | * @return $this
115 | */
116 | public function markPersisted(string $key, int $index): static
117 | {
118 | // Find if there is a key already for the persisted alert and replace it.
119 | $this->abandon($key);
120 |
121 | $this->persisted[$key] = $index;
122 |
123 | return $this;
124 | }
125 |
126 | /**
127 | * Abandons a persisted Alert.
128 | *
129 | * @param string $key
130 | * @return bool Returns true if successful.
131 | */
132 | public function abandon(string $key): bool
133 | {
134 | if (null !== $index = $this->whichPersistent($key)) {
135 | $this->alerts->forget($index);
136 | unset($this->persisted[$key]);
137 | return true;
138 | }
139 |
140 | return false;
141 | }
142 |
143 | /**
144 | * Check if an Alert by the given key is persistent.
145 | *
146 | * @param string $key
147 | * @return bool
148 | */
149 | public function hasPersistent(string $key): bool
150 | {
151 | return null !== $this->whichPersistent($key);
152 | }
153 |
154 | /**
155 | * Locates the key of a persistent alert.
156 | *
157 | * @param string $key
158 | * @return int|null
159 | */
160 | protected function whichPersistent(string $key): ?int
161 | {
162 | return $this->persisted[$key] ?? null;
163 | }
164 |
165 | /**
166 | * Deletes all alerts.
167 | *
168 | * @return void
169 | */
170 | public function flush(): void
171 | {
172 | $this->alerts = new Collection();
173 | }
174 |
175 | /**
176 | * Creates an Alert only if the condition evaluates to true.
177 | *
178 | * @param \Closure|bool $condition
179 | * @return \DarkGhostHunter\Laralerts\Alert
180 | */
181 | public function when(Closure|bool $condition): Alert
182 | {
183 | return value($condition, $this) ? $this->new() : new BogusAlert($this);
184 | }
185 |
186 | /**
187 | * Creates an Alert only if the condition evaluates to false.
188 | *
189 | * @param \Closure|bool $condition
190 | * @return \DarkGhostHunter\Laralerts\Alert
191 | */
192 | public function unless(Closure|bool $condition): Alert
193 | {
194 | return ! value($condition, $this) ? $this->new() : new BogusAlert($this);
195 | }
196 |
197 | /**
198 | * Adds an Alert into the bag from a JSON string.
199 | *
200 | * @param string $alert
201 | * @param int $options
202 | * @return \DarkGhostHunter\Laralerts\Alert
203 | * @throws \JsonException
204 | */
205 | public function fromJson(string $alert, int $options = 0): Alert
206 | {
207 | $this->add($instance = Alert::fromArray($this, json_decode($alert, true, 512, $options | JSON_THROW_ON_ERROR)));
208 |
209 | return $instance;
210 | }
211 |
212 | /**
213 | * Pass through all calls to a new Alert.
214 | *
215 | * @codeCoverageIgnore
216 | * @param string $method
217 | * @param array $parameters
218 | * @return \DarkGhostHunter\Laralerts\Alert
219 | */
220 | public function __call(string $method, array $parameters)
221 | {
222 | if (static::hasMacro($method)) {
223 | return $this->macroCall($method, $parameters);
224 | }
225 |
226 | return $this->new()->{$method}(...$parameters);
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/src/Testing/Builder.php:
--------------------------------------------------------------------------------
1 | message = $message;
59 |
60 | return $this;
61 | }
62 |
63 | /**
64 | * Expect an alert with the message.
65 | *
66 | * @param string $message
67 | * @return $this
68 | */
69 | public function withMessage(string $message): static
70 | {
71 | return $this->withRaw(e($message));
72 | }
73 |
74 | /**
75 | * Expect an alert with a translated message.
76 | *
77 | * @param string $key
78 | * @param array $replace
79 | * @param string|null $locale
80 | * @return $this
81 | */
82 | public function withTrans(string $key, array $replace = [], string $locale = null): static
83 | {
84 | return $this->withRaw(trans($key, $replace, $locale));
85 | }
86 |
87 | /**
88 | * Expect an alert with a translated (choice) message.
89 | *
90 | * @param string $key
91 | * @param Countable|int|array $number
92 | * @param array $replace
93 | * @param string|null $locale
94 | * @return $this
95 | */
96 | public function withTransChoice(
97 | string $key,
98 | Countable|int|array $number,
99 | array $replace = [],
100 | string $locale = null
101 | ): static
102 | {
103 | return $this->withRaw(trans_choice($key, $number, $replace, $locale));
104 | }
105 |
106 | /**
107 | * Expect an alert with a link away.
108 | *
109 | * @param string $replace
110 | * @param string $url
111 | * @param bool $blank
112 | * @return $this
113 | */
114 | public function withAway(string $replace, string $url, bool $blank = true): static
115 | {
116 | $this->links[] = (object) [
117 | 'replace' => $replace,
118 | 'url' => $url,
119 | 'blank' => $blank,
120 | ];
121 |
122 | usort($this->links, static function (object $first, object $second): int {
123 | return strcmp($first->replace . $first->url, $second->replace . $second->url);
124 | });
125 |
126 | return $this;
127 | }
128 |
129 | /**
130 | * Expect an alert with a link to a path.
131 | *
132 | * @param string $replace
133 | * @param string $url
134 | * @param bool $blank
135 | * @return $this
136 | */
137 | public function withTo(string $replace, string $url, bool $blank = false): static
138 | {
139 | return $this->withAway($replace, url($url), $blank);
140 | }
141 |
142 | /**
143 | * Expect an alert with a link to a route.
144 | *
145 | * @param string $replace
146 | * @param string $name
147 | * @param array $parameters
148 | * @param bool $blank
149 | * @return $this
150 | */
151 | public function withRoute(string $replace, string $name, array $parameters = [], bool $blank = false): static
152 | {
153 | return $this->withAway($replace, route($name, $parameters), $blank);
154 | }
155 |
156 | /**
157 | * Expect an alert with a link to an action.
158 | *
159 | * @param string $replace
160 | * @param string|array $action
161 | * @param array $parameters
162 | * @param bool $blank
163 | * @return $this
164 | */
165 | public function withAction(string $replace, string|array $action, array $parameters = [], bool $blank = false): static
166 | {
167 | return $this->withAway($replace, action($action), $blank);
168 | }
169 |
170 | /**
171 | * Expect an alert with the issued types.
172 | *
173 | * @param string ...$types
174 | * @return $this
175 | */
176 | public function withTypes(string ...$types): static
177 | {
178 | $this->types = $types;
179 |
180 | sort($this->types);
181 |
182 | return $this;
183 | }
184 |
185 | /**
186 | * Expect an alert persisted.
187 | *
188 | * @return $this
189 | */
190 | public function persisted(): static
191 | {
192 | $this->persisted = true;
193 |
194 | return $this;
195 | }
196 |
197 | /**
198 | * Expect an alert not persisted.
199 | *
200 | * @return $this
201 | */
202 | public function notPersisted(): static
203 | {
204 | $this->persisted = false;
205 |
206 | return $this;
207 | }
208 |
209 | /**
210 | * Expect an alert dismissible.
211 | *
212 | * @return $this
213 | */
214 | public function dismissible(): static
215 | {
216 | $this->dismiss = true;
217 |
218 | return $this;
219 | }
220 |
221 | /**
222 | * Expect an alert not dismissible.
223 | *
224 | * @return $this
225 | */
226 | public function notDismissible(): static
227 | {
228 | $this->dismiss = false;
229 |
230 | return $this;
231 | }
232 |
233 | /**
234 | * Expect an alert with the given tags.
235 | *
236 | * @param string ...$tags
237 | * @return $this
238 | */
239 | public function withTag(string ...$tags): static
240 | {
241 | $this->tags = $tags;
242 |
243 | return $this;
244 | }
245 |
246 | /**
247 | * Expect an alert with any of the given tags.
248 | *
249 | * @param string ...$tags
250 | * @return $this
251 | */
252 | public function withAnyTag(string ...$tags): static
253 | {
254 | $this->anyTag = true;
255 |
256 | return $this->withTag(...$tags);
257 | }
258 | /**
259 | * Returns a collection of all matching alerts.
260 | *
261 | * @return \Illuminate\Support\Collection
262 | */
263 | protected function matches(): Collection
264 | {
265 | return $this->bag->added->filter(function (Alert $alert): bool {
266 | return $this->is($alert);
267 | });
268 | }
269 |
270 | /**
271 | * Check if the given alert matches the expectations.
272 | *
273 | * @param \DarkGhostHunter\Laralerts\Alert $alert
274 | * @return bool
275 | */
276 | protected function is(Alert $alert): bool
277 | {
278 | if ($this->message !== null && $this->message !== $alert->getMessage()) {
279 | return false;
280 | }
281 |
282 | if ($this->dismiss !== null && $this->dismiss !== $alert->isDismissible()) {
283 | return false;
284 | }
285 |
286 | if ($this->types !== null && $this->types !== $alert->getTypes()) {
287 | return false;
288 | }
289 |
290 | if ($this->tags !== null) {
291 | if ($this->anyTag) {
292 | return $alert->hasAnyTag(...$this->tags);
293 | }
294 |
295 | return $this->tags === $alert->getTags();
296 | }
297 |
298 | if ($this->persisted !== null) {
299 | if (is_string($this->persisted)) {
300 | return $this->persisted === $alert->getPersistKey();
301 | }
302 |
303 | if (is_array($this->persisted)) {
304 | return in_array($alert->getPersistKey(), $this->persisted, true);
305 | }
306 |
307 | return $this->persisted === (bool) $alert->getPersistKey();
308 | }
309 |
310 | if ($this->links !== null && $this->links != $alert->getLinks()) {
311 | return false;
312 | }
313 |
314 | return true;
315 | }
316 |
317 | /**
318 | * Expect an alert persisted with the issued key.
319 | *
320 | * @param string|array $key
321 | * @return void
322 | */
323 | public function persistedAs(string ...$key): void
324 | {
325 | $this->persisted = $key;
326 |
327 | $count = count($key);
328 |
329 | $this->count($count, "Failed to assert that [$count] persistent alerts exist.");
330 | }
331 |
332 | /**
333 | * Assert that at least one Alert exists with the given expectations.
334 | *
335 | * @param string $message
336 | * @return void
337 | */
338 | public function exists(string $message = 'Failed to assert that at least one alert matches the expectations.'): void
339 | {
340 | PHPUnit::assertNotEmpty($this->matches(), $message);
341 | }
342 |
343 | /**
344 | * Assert that no Alert exists with the given expectations.
345 | *
346 | * @param string $message
347 | * @return void
348 | */
349 | public function missing(string $message = 'Failed to assert that no alert matches the expectations.'): void
350 | {
351 | PHPUnit::assertEmpty($this->matches(), $message);
352 | }
353 |
354 | /**
355 | * Assert that only one Alert exists with the given expectations.
356 | *
357 | * @param string $message
358 | * @return void
359 | */
360 | public function unique(string $message = 'Failed to assert that there is only one alert.'): void
361 | {
362 | $this->count(1, $message);
363 | }
364 |
365 | /**
366 | * Assert that the given number of Alerts matches exactly the given expectations.
367 | *
368 | * @param int $count
369 | * @param string|null $message
370 | * @return void
371 | */
372 | public function count(int $count, string $message = null): void
373 | {
374 | $matches = $this->matches();
375 |
376 | PHPUnit::assertCount(
377 | $count, $matches,
378 | $message ?? "Failed to assert that [{$matches->count()}] alerts match the expected [$count] count."
379 | );
380 | }
381 | }
382 |
--------------------------------------------------------------------------------
/src/Alert.php:
--------------------------------------------------------------------------------
1 | bag = $bag;
65 |
66 | return $this;
67 | }
68 |
69 | /**
70 | * Returns the key used to persist the alert, if any.
71 | *
72 | * @return string|null
73 | * @internal
74 | */
75 | public function getPersistKey(): ?string
76 | {
77 | return $this->persistKey;
78 | }
79 |
80 | /**
81 | * Returns the message of the Alert.
82 | *
83 | * @return string
84 | * @internal
85 | */
86 | public function getMessage(): string
87 | {
88 | return $this->message;
89 | }
90 |
91 | /**
92 | * Returns the types set for this Alert.
93 | *
94 | * @return string[]
95 | * @internal
96 | */
97 | public function getTypes(): array
98 | {
99 | return $this->types;
100 | }
101 |
102 | /**
103 | * Returns the links to replace in the message.
104 | *
105 | * @return array
106 | * @internal
107 | */
108 | public function getLinks(): array
109 | {
110 | return $this->links;
111 | }
112 |
113 | /**
114 | * Check if the Alert should be dismissible.
115 | *
116 | * @return bool
117 | * @internal
118 | */
119 | public function isDismissible(): bool
120 | {
121 | return $this->dismissible;
122 | }
123 |
124 | /**
125 | * Returns the tags of this Alert.
126 | *
127 | * @return array
128 | * @internal
129 | */
130 | public function getTags(): array
131 | {
132 | return $this->tags;
133 | }
134 |
135 | /**
136 | * Check if the alert contains any of the given tags.
137 | *
138 | * @param string ...$tags
139 | * @return bool
140 | * @internal
141 | */
142 | public function hasAnyTag(string ...$tags): bool
143 | {
144 | foreach ($tags as $tag) {
145 | if (in_array($tag, $this->tags, true)) {
146 | return true;
147 | }
148 | }
149 |
150 | return false;
151 | }
152 |
153 | /**
154 | * Sets a safely-escaped message.
155 | *
156 | * @param string $message
157 | * @return $this
158 | */
159 | public function message(string $message): static
160 | {
161 | return $this->raw(e($message));
162 | }
163 |
164 | /**
165 | * Sets a raw, non-escaped, message.
166 | *
167 | * @param string $message
168 | * @return $this
169 | */
170 | public function raw(string $message): static
171 | {
172 | $this->message = $message;
173 |
174 | return $this;
175 | }
176 |
177 | /**
178 | * Set a localized message into the Alert.
179 | *
180 | * @param string $key
181 | * @param array $replace
182 | * @param string|null $locale
183 | * @return $this
184 | */
185 | public function trans(string $key, array $replace = [], string $locale = null): static
186 | {
187 | return $this->raw(trans($key, $replace, $locale));
188 | }
189 |
190 | /**
191 | * Sets a localized pluralized message into the Alert.
192 | *
193 | * @param string $key
194 | * @param \Countable|int|array $number
195 | * @param array $replace
196 | * @param string|null $locale
197 | * @return $this
198 | */
199 | public function transChoice(
200 | string $key,
201 | Countable|int|array $number,
202 | array $replace = [],
203 | string $locale = null
204 | ): static {
205 | return $this->raw(trans_choice($key, $number, $replace, $locale));
206 | }
207 |
208 | /**
209 | * Sets one or many types for this alert.
210 | *
211 | * @param string ...$types
212 | * @return $this
213 | */
214 | public function types(string ...$types): static
215 | {
216 | $this->types = $types;
217 |
218 | sort($this->types);
219 |
220 | return $this;
221 | }
222 |
223 | /**
224 | * Sets the Alert as dismissible.
225 | *
226 | * @param bool $dismissible
227 | * @return $this
228 | */
229 | public function dismiss(bool $dismissible = true): static
230 | {
231 | $this->dismissible = $dismissible;
232 |
233 | return $this;
234 | }
235 |
236 | /**
237 | * Persists the key into the session, forever.
238 | *
239 | * @param string $key
240 | * @return $this
241 | */
242 | public function persistAs(string $key): static
243 | {
244 | $this->persistKey = $key;
245 |
246 | $this->bag->markPersisted($key, $this->index);
247 |
248 | return $this;
249 | }
250 |
251 | /**
252 | * Abandons the Alert from persistence.
253 | *
254 | * @return $this
255 | */
256 | public function abandon(): static
257 | {
258 | $this->bag->abandon($this->persistKey);
259 |
260 | $this->persistKey = null;
261 |
262 | return $this;
263 | }
264 |
265 | /**
266 | * Adds an external link that should be replaced before rendering the Alert.
267 | *
268 | * @param string $replace
269 | * @param string $url
270 | * @param bool $blank
271 | * @return $this
272 | */
273 | public function away(string $replace, string $url, bool $blank = true): static
274 | {
275 | $this->links[] = (object) [
276 | 'replace' => trim($replace, "{}"),
277 | 'url' => $url,
278 | 'blank' => $blank,
279 | ];
280 |
281 | usort($this->links, static function (object $first, object $second): int {
282 | return strcmp($first->replace . $first->url, $second->replace . $second->url);
283 | });
284 |
285 | return $this;
286 | }
287 |
288 | /**
289 | * Adds a link that should be replaced before rendering the Alert.
290 | *
291 | * @param string $replace
292 | * @param string $url
293 | * @param bool $blank
294 | * @return $this
295 | */
296 | public function to(string $replace, string $url, bool $blank = false): static
297 | {
298 | return $this->away($replace, url($url), $blank);
299 | }
300 |
301 | /**
302 | * Adds a link to a route that should be replaced before rendering the Alert.
303 | *
304 | * @param string $replace
305 | * @param string $name
306 | * @param array $parameters
307 | * @param bool $blank
308 | * @return $this
309 | */
310 | public function route(string $replace, string $name, array $parameters = [], bool $blank = false): static
311 | {
312 | return $this->away($replace, route($name, $parameters), $blank);
313 | }
314 |
315 | /**
316 | * Adds a link to an action that should be replaced before rendering the Alert.
317 | *
318 | * @param string $replace
319 | * @param string|array $action
320 | * @param array $parameters
321 | * @param bool $blank
322 | * @return $this
323 | */
324 | public function action(string $replace, string|array $action, array $parameters = [], bool $blank = false): static
325 | {
326 | return $this->away($replace, action($action, $parameters), $blank);
327 | }
328 |
329 | /**
330 | * Tags the alert.
331 | *
332 | * @param string ...$tags
333 | * @return $this
334 | */
335 | public function tag(string ...$tags): static
336 | {
337 | $this->tags = $tags;
338 |
339 | sort($this->tags);
340 |
341 | return $this;
342 | }
343 |
344 | /**
345 | * Get the instance as an array.
346 | *
347 | * @return array
348 | */
349 | public function toArray(): array
350 | {
351 | return [
352 | 'message' => $this->message,
353 | 'types' => $this->types,
354 | 'dismissible' => $this->dismissible,
355 | ];
356 | }
357 |
358 | /**
359 | * Convert the object to its JSON representation.
360 | *
361 | * @param int $options
362 | * @return string
363 | * @throws \JsonException
364 | */
365 | public function toJson($options = 0): string
366 | {
367 | return json_encode($this->jsonSerialize(), $options | JSON_THROW_ON_ERROR);
368 | }
369 |
370 | /**
371 | * Specify data which should be serialized to JSON.
372 | *
373 | * @return array
374 | */
375 | public function jsonSerialize(): array
376 | {
377 | return $this->toArray();
378 | }
379 |
380 | /**
381 | * Returns the string representation of the Alert.
382 | *
383 | * @return string
384 | * @throws \JsonException
385 | */
386 | public function __toString()
387 | {
388 | return $this->toJson();
389 | }
390 |
391 | /**
392 | * Serializes the Alert.
393 | *
394 | * @codeCoverageIgnore
395 | * @return array
396 | */
397 | public function __serialize(): array
398 | {
399 | return [
400 | 'persistKey' => $this->persistKey,
401 | 'message' => $this->message,
402 | 'types' => $this->types,
403 | 'links' => $this->links,
404 | 'dismissible' => $this->dismissible,
405 | 'tags' => $this->tags,
406 | ];
407 | }
408 |
409 | /**
410 | * Unserializes the alert.
411 | *
412 | * @codeCoverageIgnore
413 | * @param array $data
414 | */
415 | public function __unserialize(array $data): void
416 | {
417 | $this->persistKey = $data['persistKey'];
418 | $this->message = $data['message'];
419 | $this->types = $data['types'];
420 | $this->links = $data['links'];
421 | $this->dismissible = $data['dismissible'];
422 | $this->tags = $data['tags'];
423 | }
424 |
425 | /**
426 | * Creates a new Alert from a Bag and an array.
427 | *
428 | * @param \DarkGhostHunter\Laralerts\Bag|array $bag
429 | * @param array|null $alert
430 | * @return \DarkGhostHunter\Laralerts\Alert
431 | */
432 | public static function fromArray(Bag|array $bag, array $alert = null): Alert
433 | {
434 | if (is_array($bag)) {
435 | [$bag, $alert] = [app(Bag::class), $bag];
436 | }
437 |
438 | return new static($bag, null, $alert['message'], $alert['types'], [], $alert['dismissible'] ?? false);
439 | }
440 | }
441 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Package superseeded by [Laragear/Alerts](https://github.com/Laragear/Alerts)
2 |
3 | ---
4 |
5 | # Laralerts
6 |
7 | Quickly set one or multiple Alerts in your backend, and render them in the frontend.
8 |
9 | Laralerts is compatible with **any** frontend framework to better suit your app, not the other way around.
10 |
11 | ## Requirements
12 |
13 | * Laravel 8.x or later
14 | * PHP 8.0 or later.
15 |
16 | > For older versions support, consider helping by sponsoring or donating.
17 |
18 | ## Installation
19 |
20 | You can install the package via composer:
21 |
22 | ```bash
23 | composer require darkghosthunter/laralerts
24 | ```
25 |
26 | If you don't have anything to start with in your frontend, you can use [Laravel Jetstream](https://jetstream.laravel.com/), or go the classic way and use [Bootstrap](https://getbootstrap.com), [Bulma.io](https://bulma.io/), [UI kit](https://getuikit.com/), [TailwindCSS](https://tailwindcss.com/) and [INK](http://ink.sapo.pt/), among many others.
27 |
28 | ## Usage
29 |
30 | Laralerts allows you to set a list of Alerts in your application and render them in the frontend in just a few minutes.
31 |
32 | The default Alert renderer uses Bootstrap code to transform each alert into [Bootstrap Alerts](https://getbootstrap.com/docs/5.0/components/alerts/). If you're not using Bootstrap, you can [create your own](#creating-a-custom-renderer) for your particular framework.
33 |
34 | ### Quickstart
35 |
36 | To set an Alert in your frontend, you can use the `alert()` helper, or the `Alert` Facade. A good place to use them is before sending a response to the browser, like in your HTTP Controllers.
37 |
38 | If you're sending a redirect, Laralerts will automatically flash the alert so the next request can show it.
39 |
40 | ```php
41 | validate([
60 | 'title' => 'required|string|max:255',
61 | 'body' => 'required|string'
62 | ]);
63 |
64 | $article->fill($request)->save();
65 |
66 | alert('Your article has been updated!', 'success');
67 |
68 | return redirect()->action('ArticleController@edit', $article);
69 | }
70 | }
71 | ```
72 |
73 | The `alert()` helper accepts the text *message* and the **types** of the alert. In the above example, we created a "success" alert.
74 |
75 | To render them in the frontend, use the `` Blade component which will take care of the magic, anywhere you want to put it.
76 |
77 | ```blade
78 |
79 |
Welcome to my site
80 |
81 |
82 | ```
83 |
84 | If there is at least one Alert to be rendered, the above will be transformed into proper HTML:
85 |
86 | ```html
87 |
88 |
Welcome to my site
89 |
90 |
91 | Your article has been updated!
92 |
93 |
94 |
95 | ```
96 |
97 | ### Message
98 |
99 | Add the text inside the Alert using the `message()` method. That's it.
100 |
101 | ```php
102 | message('You are gonna love this! 😍')->types('success');
107 |
108 | alert()->message('We will email you 📨 a copy!')->types('info');
109 | ```
110 |
111 | ```html
112 |
113 | You are gonna love this! 😍
114 |
115 |
116 |
117 | We will email you 📨 a copy!
118 |
119 | ```
120 |
121 | > By default, the `message()` method escapes the text. If you want to send a raw message, you should use [`raw()`](#raw-message).
122 |
123 | ### Raw message
124 |
125 | Since the `message()` method escapes the text for safety, you can use the `raw()` method to output a string verbatim. This allows you to use HTML for personalized messages, like adding some _style_, links, or even scripts.
126 |
127 | ```php
128 | message('This is FUBAR.')->types('warning');
131 |
132 | alert()->raw('But this is important.')->types('warning');
133 | ```
134 |
135 | ```html
136 |
137 | This is <strong>FUBAR</strong>.
138 |
139 |
140 |
141 | But this is important.
142 |
143 | ```
144 |
145 | **Warning: Don't use `raw()` to show user-generated content. YOU HAVE BEEN WARNED**.
146 |
147 | ### Alert Type
148 |
149 | You can set an alert "type" by its name by just setting it with the `types()` method. It also accepts multiple types.
150 |
151 | ```php
152 | message('Your message was sent!')->types('primary');
155 |
156 | alert()->message('There is an unread message.')->types('info', 'fade');
157 | ```
158 |
159 | ```html
160 |
161 | Your message was sent!
162 |
163 |
164 | There is an unread message.
165 |
166 | ```
167 |
168 | The types are just aliases for custom CSS classes and HTML, which are then translated by the Renderer to the proper code.
169 |
170 | > The Renderer receives the list of types and changes them into CSS classes accordingly. The default Bootstrap Renderer will set each unrecognized type as an additional CSS class.
171 |
172 | ### Localization
173 |
174 | To gracefully localize messages on the fly, use the `trans()` method, which is a mirror of [the `__()` helper](https://laravel.com/docs/localization#retrieving-translation-strings).
175 |
176 | ```php
177 | trans('email.changed', ['email' => $email], 'es')->types('success');
180 | ```
181 |
182 | ```html
183 |
184 | ¡Tu email ha sido cambiado a "margarita@madrid.cl" con éxito!
185 |
186 | ```
187 |
188 | You can also use `transChoice()` with the same parameters of [`trans_choice()`](https://laravel.com/docs/localization#pluralization).
189 |
190 | ```php
191 | transChoice('messages.apples', 1)->types('success');
194 | alert()->transChoice('messages.apples', 10)->types('success');
195 | ```
196 |
197 | ```html
198 |
199 | ¡Ahora tienes 1 manzana!
200 |
201 |
202 |
203 | ¡Ahora tienes 10 manzanas!
204 |
205 | ```
206 |
207 | ### Dismiss
208 |
209 | Most of the frontend frameworks have alerts or notifications that can be dismissible, but require adding more than a single class to allow for interactivity. You can set an Alert to be dismissible using `dismiss()`.
210 |
211 | ```php
212 | alert()->message('You can disregard this')->type('success')->dismiss();
213 | ```
214 |
215 | If you want to change your mind, you can use `dismiss(false)`:
216 |
217 | ```php
218 | alert()->message('You can disregard this')->type('success')->dismiss(false);
219 | ```
220 |
221 | How the dismissible alert is transformed into code will depend on the renderer itself. The default Bootstrap renderer adds the proper CSS classes and a dismiss button automatically.
222 |
223 | ```html
224 |
225 | You can disregard this
226 |
227 |
228 | ```
229 |
230 | ### Conditional Alerts
231 |
232 | You can also push an Alert if a condition evaluates to true or false by using `when()` and `unless()`, respectively. Further method calls will be sent to the void.
233 |
234 | ```php
235 | when(Auth::check())
240 | ->message('You are authenticated')
241 | ->types('success');
242 |
243 | alert()->unless(Auth::user()->mailbox()->isNotEmpty())
244 | ->message('You have messages in your inbox')
245 | ->types('warning');
246 | ```
247 |
248 | ### Persistent Alerts
249 |
250 | Alerts only last for the actual request being sent. On redirects, these are [flashed into the session](https://laravel.com/docs/8.x/session#flash-data) so these are available on the next request (the redirection target).
251 |
252 | To make any alert persistent you can use the `persistAs()` method with a key to identify the alert.
253 |
254 | ```php
255 | alert()->message('Your disk size is almost full')->types('danger')->persistAs('disk.full');
256 | ```
257 |
258 | > Setting a persistent alert replaces any previous set with the same key.
259 |
260 | Once you're done, you can delete the persistent Alert using `abandon()` directly from the helper using the key of the persisted Alert. It will return `true` if the persisted Alert is found, or `false` if not. For example, we can abandon the previous alert if the disk is no longer full.
261 |
262 | ```php
263 | if ($disk->notFull()) {
264 | alert()->abandon('disk.full');
265 | }
266 | ```
267 |
268 | ### Links
269 |
270 | Setting up links for an alert doesn't have to be cumbersome. You can easily replace a string between curly braces in your message for a link using `to()`, `route()`, `action()`, and `away()`.
271 |
272 | ```php
273 | message('Remember, you can follow your order in your {dashboard}.')
276 | ->types('success')
277 | ->to('dashboard', '/dashboard/orders')
278 | ```
279 |
280 | Links can also work over translated messages, as long these have a word in curly braces.
281 |
282 | ```php
283 | trans('user.dashboard.tracking.order', ['order' => $order->tracking_number])
287 | ->types('success')
288 | ->route('tracking', 'orders.tracking', ['order' => 45])
289 | ```
290 |
291 | If you have more than one link, you can chain multiple links to a message.
292 |
293 | ```php
294 | trans('Your {product} is contained in this {order}.')
297 | ->types('success')
298 | ->action('product', [\App\Http\Controllers\Product::class, 'show'], ['product' => 180])
299 | ->to('order', '/dashboard/order/45')
300 | ```
301 |
302 | > Links strings are case-sensitive, and replaces all occurrences of the same string. You can [create your own Renderer](#creating-a-custom-renderer) if this is not desired.
303 |
304 | ### Tags
305 |
306 | Sometimes you may have more than one place in your site to place Alerts, like one for global alerts and other for small user alerts. Tags can work to filter which Alerts you want to render.
307 |
308 | You can set the tags of the Alert using `tag()`.
309 |
310 | ```php
311 | alert()->message('Maintenance is scheduled for tomorrow')
312 | ->type('warning')
313 | ->tag('user', 'admin')
314 | ```
315 |
316 | Using the [Laralerts directive](#quickstart), you can filter the Alerts to render by the tag names using the `:tags` slot.
317 |
318 | ```blade
319 |
320 |
321 |
322 |
323 |
324 | ```
325 |
326 | ## Configuration
327 |
328 | Laralerts works out-of-the-box with some common defaults, but if you need a better approach for your particular application, you can configure some parameters. First, publish the configuration file.
329 |
330 | ```bash
331 | php artisan vendor:publish --provider="DarkGhostHunter\Laralerts\LaralertsServiceProvider" --tag="config"
332 | ```
333 |
334 | Let's examine the configuration array, which is quite simple:
335 |
336 | ```php
337 | 'bootstrap',
341 | 'key' => '_alerts',
342 | 'tags' => 'default',
343 | ];
344 | ```
345 |
346 | ### Renderer
347 |
348 | ```php
349 | 'bootstrap',
353 | ];
354 | ```
355 |
356 | This picks the Renderer to use when transforming Alerts into HTML.
357 |
358 | This package ships with Bootstrap 5 renderer, but you can [create your own](#renderers) for other frontend frameworks like [Bulma.io](https://bulma.io/), [UI kit](https://getuikit.com/), [TailwindCSS](https://tailwindcss.com/) and [INK](http://ink.sapo.pt/), or even your own custom frontend framework.
359 |
360 | ### Session Key
361 |
362 | ```php
363 | '_alerts',
367 | ];
368 | ```
369 |
370 | When alerts are flashed or persisted, these are stored in the Session by a given key, which is `_alerts` by default. If you're using this key name for other things, you may want to change it.
371 |
372 | This key is also used when [sending JSON alerts](#sending-json-alerts).
373 |
374 | ### Default tag list
375 |
376 | ```php
377 | ['user', 'admin'],
381 | ];
382 | ```
383 |
384 | This holds the default tag list to inject to all Alerts when created. You can leave this alone if you're not using [tags](#tags).
385 |
386 | ## Renderers
387 |
388 | A Renderer takes a [collection](https://laravel.com/docs/collections) of Alerts and transforms each into an HTML string. This makes swapping a frontend framework easier, and allows greater flexibility when rendering HTML.
389 |
390 | ### Creating a custom renderer
391 |
392 | You can create your own using the `Renderer` contract, and registering it into the `RendererManager` in your `AppServiceProvider`. You can use the `BootstrapRenderer` as a starting point.
393 |
394 | ```php
395 | extend('tailwind', function ($app) {
408 | return new TailwindRenderer($app['blade.compiler']);
409 | });
410 | }
411 | ```
412 |
413 | Then, in your config file, set the renderer to the one you have registered.
414 |
415 | ```php
416 | // config/laralerts.php
417 |
418 | return [
419 | 'renderer' => 'tailwind'
420 |
421 | // ...
422 | ];
423 | ```
424 |
425 | When you issue an alert, the alert will be rendered using your own custom renderer.
426 |
427 | ```php
428 | message('Popping colors!')->types('primary');
431 | ```
432 |
433 | ```html
434 |
435 | Popping colors!
436 |
437 | ```
438 |
439 | ### Alerts Container HTML
440 |
441 | When the Renderer receives Alerts to render, it will call a "container" view which will render all the Alerts by using a loop.
442 |
443 | For example, the included `BootstrapRenderer` calls the `laralerts::bootstrap.container`.
444 |
445 | ```html
446 | @if($alerts->isNotEmpty())
447 |
450 | @endif
451 | ```
452 |
453 | You may be using another frontend framework different from Bootstrap 5, or you may want to change the HTML to better suit your application design. In any case, you can override the View files in `views/vendor/laralerts`:
454 |
455 | * `container.blade.php`: The HTML that contains all the Alerts.
456 | * `alert.blade.php`: The HTML for a single Alert.
457 |
458 | The variables the `alert.blade.php` view receives are set from by Renderer. For the case of the included Bootstrap renderer, these are:
459 |
460 | * `$alert->message`: The message to show inside the Alert.
461 | * `$alert->classes`: The CSS classes to incorporate into the Alert.
462 | * `$alert->dismissible`: A boolean that sets the alert as dismissible or not.
463 |
464 | As you're suspecting, you can publish the views and override them to suit your needs.
465 |
466 | php artisan vendor:publish --provider="DarkGhostHunter\Laralerts\LaralertsServiceProvider" --tag="views"
467 |
468 | ## JSON Alerts
469 |
470 | ### Receiving JSON Alerts
471 |
472 | Sometimes your application may receive a JSON Alert from an external service using this package. You can quickly add this JSON as an Alert to your application using the `fromJson()` method.
473 |
474 | ```json
475 | {
476 | "alert": {
477 | "message": "Email delivered",
478 | "types": [
479 | "success",
480 | "important"
481 | ],
482 | "dismissible": false
483 | }
484 | }
485 | ```
486 |
487 | ```php
488 | alert()->fromJson($json);
489 | ```
490 |
491 | This will work as long the JSON **has the `message` key** with the text to include inside the Alert. Additionally, you can add the `types` and `dismiss` keys to add an Alert, with the possibility of override them afterwards.
492 |
493 | ### Sending JSON Alerts
494 |
495 | This library has a convenient way to add Alerts into your JSON Responses. This can be very useful to add your alerts to each response being sent to the browser, like combining this package with [Laravel Jetstream](https://jetstream.laravel.com/).
496 |
497 | Just simply [add the `laralerts.json` middleware](https://laravel.com/docs/middleware#registering-middleware) into your `api` routes or, if you're using [Laravel Jetstream](https://jetstream.laravel.com/) or similar, as a [global middleware](https://laravel.com/docs/8.x/middleware#global-middleware).
498 |
499 | When you return a `JsonResponse` to the browser, the middleware will append the alert as JSON using the same [session key](#session-key) defined in the configuration, which is `_alerts` by default. It also accepts the `key` parameter to use as an alternative, compatible with *dot notation*. Here is an example:
500 |
501 | ```php
502 | uses('UserController@create');
509 | Route::post('update')->uses('UserController@update');
510 |
511 | })->middleware('laralerts.json:_status.alerts');
512 | ```
513 |
514 | When you receive a JSON Response, you will see the alerts appended to whichever key you issued. Using the above example, we should see the `alerts` key under the `_status` key:
515 |
516 | ```json
517 | {
518 | "resource": "users",
519 | "url": "/v1/users",
520 | "_status": {
521 | "timestamp": "2019-06-05T03:47:24Z",
522 | "action" : "created",
523 | "id": 648,
524 | "alerts": [
525 | {
526 | "message": "The user has been created!",
527 | "types" : ["success", "important"],
528 | "dismiss": true
529 | }
530 | ]
531 | }
532 | }
533 | ```
534 |
535 | > If your key is already present in the JSON response, Laralerts **won't overwrite the key value**. Ensure the key is never present in the response.
536 |
537 | ## Testing
538 |
539 | To test if alerts were generated, you can use `Alert::fake()`, which works like any other faked services. It returns a fake Alert Bag that holds a copy of all alerts generated, which exposes some convenient assertion methods.
540 |
541 | ```php
542 | use \DarkGhostHunter\Laralerts\Facades\Alert;
543 |
544 | public function test_alert_sent()
545 | {
546 | $alerts = Alert::fake();
547 |
548 | $this->post('/comment', ['body' => 'cool'])->assertOk();
549 |
550 | $alerts->assertHasOne();
551 | }
552 | ```
553 |
554 | The following assertions are available:
555 |
556 | | Method | Description |
557 | |---------------------------|------------------------------------------------------------------------|
558 | | `assertEmpty()` | Check if the alert bag doesn't contains alerts. |
559 | | `assertNotEmpty()` | Check if the alert bag contains any alert. |
560 | | `assertHasOne()` | Check if the alert bag contains only one alert. |
561 | | `assertHas($count)` | Check if the alert bag contains the exact amount of alerts. |
562 | | `assertHasPersistent()` | Check if the alert bag contains at least one persistent alert. |
563 | | `assertHasNoPersistent()` | Check if the alert bag doesn't contains a persistent alert. |
564 | | `assertPersistentCount()` | Check if the alert bag contains the exact amount of persistent alerts. |
565 |
566 | ### Asserting specific alerts
567 |
568 | The fake Alert bag allows building conditions for the existence (or nonexistence) of alerts with specific properties, by using `assertAlert()`.
569 |
570 | Once you build your conditions, you can use `exists()` to check if any alert matches, or `missing()` to check if no alert should match.
571 |
572 | ```php
573 | $bag->assertAlert()->withMessage('Hello world!')->exists();
574 |
575 | $bag->assertAlert()->withTypes('danger')->dismissible()->missing();
576 | ```
577 |
578 | Alternatively, you can use `count()` if you expect a specific number of alerts to match the given conditions, or `unique()` for matching only one alert.
579 |
580 | ```php
581 | $bag->assertAlert()->persisted()->count(2);
582 |
583 | $bag->assertAlert()->notDismissible()->withTag('toast')->unique();
584 | ```
585 |
586 | The following conditions are available:
587 |
588 | | Method | Description |
589 | |---------------------|---------------------------------------------------|
590 | | `withRaw()` | Find alerts with the given raw message. |
591 | | `withMessage()` | Find alerts with the given message. |
592 | | `withTrans()` | Find alerts with the translated message. |
593 | | `withTransChoice()` | Find alerts with the translated (choice) message. |
594 | | `withAway()` | Find alerts with a link away. |
595 | | `withTo()` | Find alerts with a link to a path. |
596 | | `withRoute()` | Find alerts with a link to a route. |
597 | | `withAction()` | Find alerts with a link to a controller action. |
598 | | `withTypes()` | Find alerts with exactly the given types. |
599 | | `persisted()` | Find alerts persisted. |
600 | | `notPersisted()` | Find alerts not persisted. |
601 | | `persistedAs()` | Find alerts persisted with the issued keys. |
602 | | `dismissible()` | Find alerts dismissible. |
603 | | `notDismissible()` | Find alerts not dismissible. |
604 | | `withTag()` | Find alerts with all the given tags. |
605 | | `withAnyTag()` | Find alerts with any of the given tags. |
606 |
607 | ## Security
608 |
609 | If you discover any security related issues, please email darkghosthunter@gmail.com instead of using the issue tracker.
610 |
611 | ## License
612 |
613 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
614 |
--------------------------------------------------------------------------------