internetguru / laravel-common
Requires
- bacon/bacon-qr-code: ^3.0
- internetguru/laravel-recaptchav3: ^1.0
- laravel/framework: ^10.0 || ^11.0 || ^12.0 || ^13.0
- livewire/livewire: ^3.6 || ^4.0
- spatie/laravel-ignition: ^2.9
- torann/geoip: ^3.0
Requires (Dev)
- laravel/pint: ^1.25
- livewire/livewire: ^3.6 || ^4.0
- orchestra/testbench: ^9.5
- phpunit/php-code-coverage: ^11.0
Suggests
- internetguru/laravel-feedback: Required by the default footer to render the feedback and complaints forms.
This package is auto-updated.
Last update: 2026-08-18 10:38:20 UTC
README
This package provides handy utilities for Laravel applications.
| Branch | Status | Code Coverage |
|---|---|---|
| Main | ||
| Staging | ||
| Dev |
Table of Contents
- Installation
- Run Tests Locally
- E2E Tests
- Service Providers
- Middleware
- Helper Methods
- Helper Macros
- Blade Components
- Casts
- Traits
- Rules
- Services
- Notifications
- Exception Handling
- Logging
- Localization
- Publishing Assets
Installation
You can install the package via Composer:
composer require internetguru/laravel-common
The CommonServiceProvider is auto-discovered via composer.json extra.laravel.providers. No manual registration is needed for the core provider.
Run Tests Locally
To run the tests manually, you can use the following command:
./test.sh
E2E Tests
The package ships reusable Playwright test helpers in tests/e2e/common-tests.js. Consuming projects can register them into their own test suite with a single call.
Setup
-
Install Playwright in your project if not already present:
npm init playwright@latest
-
In your test file, import and register the helpers:
import { test, expect } from '@playwright/test'; import { registerCommonTests } from '../../vendor/internetguru/laravel-common/tests/e2e/common-tests.js'; registerCommonTests(test, expect, { languages: { en: 'English', cs: 'Česky' }, demo: process.env.APP_DEMO === 'true', });
Options
| Option | Type | Default | Description |
|---|---|---|---|
languages |
object |
{ en: 'English', cs: 'Česky' } |
Language code → label map. Language switch tests are skipped when only one language is configured. |
demo |
boolean |
false |
When true, includes a test verifying the demo mode banner is visible. |
Covered test groups
| Group | What is tested |
|---|---|
layout |
header, main, footer presence; charset, viewport, and title meta tags. |
breadcrumb |
Visibility, item count, active state, growth on subpages. |
language switch |
Visibility, correct item count, active highlight, language change, persistence across pages. (skipped when languages has one entry) |
error pages |
401, 403, 404, 500, 503 status codes and h1 content; error index links; unknown code falls back to 404. |
messages |
.messages-wrapper is present on every page. |
demo mode |
Demo banner visible. (skipped unless demo: true) |
csrf |
CSRF token meta tag is present and non-empty. |
i18n pages |
/i18n, /i18n/complete, /i18n/missing-all, /i18n/missing-cs, /i18n/missing-en all load. |
html structure |
<html lang> attribute is set; exactly one <h1> per page. |
Service Providers
CommonServiceProvider
Auto-registered via package discovery. It provides:
- Custom exception handler registration.
- Loading of package routes, views (
ig-commonnamespace), translations, and Blade components (ignamespace). - Registration of the Livewire Messages component.
- Listener for mail logging (
NotificationSentevent). - Registration of the
ulid32validation rule. - Registration of all helper macros (String, Number, Carbon).
- Automatic registration of all middleware into the
webmiddleware group. - Queue connection safety check — throws an exception at boot if the queue connection is set to
sync(except during unit tests).
ReadOnlyServiceProvider
Intercepts all write database queries and throws
DbReadOnlyExceptionwhenconfig('app.readonly')istrue.
Read-only operations (SELECT, SHOW, DESCRIBE, EXPLAIN, PRAGMA) are always allowed. Queries targeting sessions, pin_logins, mail_logs, users, and socialites tables are whitelisted.
To use, register the provider in your config/app.php:
'providers' => ServiceProvider::defaultProviders()->merge([ InternetGuru\LaravelCommon\ReadOnlyServiceProvider::class, ])->toArray(),
Then set readonly to true in config/app.php (or via environment variable) to activate read-only mode.
TranslationServiceProvider
Logs missing translations and translation variables in the current language. Throws an exception when not in production environment. In debug mode, checks all available languages.
- Logs warning when a translation key is missing or a variable required in a translation string is not provided.
- Checks all languages in debug mode from all available locales.
- Throws exception
InternetGuru\LaravelCommon\Exceptions\TranslatorExceptioninstead of logging when the app is not in production mode.
To use the provider, replace the default TranslationServiceProvider in config/app.php:
use Illuminate\Support\ServiceProvider; 'providers' => ServiceProvider::defaultProviders()->replace([ Illuminate\Translation\TranslationServiceProvider::class => InternetGuru\LaravelCommon\TranslationServiceProvider::class, ])->toArray(),
Middleware
All middleware listed below is automatically registered in the web middleware group by the CommonServiceProvider. No manual registration is needed.
To bypass a specific middleware on a route, use the withoutMiddleware method:
Route::get('/example', ExampleController::class) ->withoutMiddleware(\InternetGuru\LaravelCommon\Http\Middleware\PreventDuplicateSubmissions::class);
CheckPostItemNames Middleware
Checks for invalid POST parameter names containing dots
".". Helps prevent issues with Laravel's input handling. Throws an exception in non-production environments and logs a warning in production.
Example:
-
When a POST request contains parameter names with dots:
POST /submit-form Content-Type: application/x-www-form-urlencoded username=johndoe&user.email=johndoe@example.com
-
In Non-Production Environments: The middleware will throw an HTTP 400 exception:
Invalid POST parameter names containing dots: user.email -
In Production Environment: The middleware will log a warning:
[WARNING] Invalid POST parameter names containing dots: user.email
InjectMetaRobots Middleware
Automatically injects a
<meta name="robots">tag into HTML responses before</head>whenMETA_ROBOTSis set.
Set the following environment variable to enable:
| Variable | Description | Default |
|---|---|---|
META_ROBOTS |
Robots directive (e.g. noindex, noindex,nofollow). |
null (disabled) |
Example .env:
META_ROBOTS=noindex,nofollow
The injected tag:
<meta name="robots" content="noindex,nofollow"/>
InjectUmamiScript Middleware
Automatically injects the Umami analytics tracking script into HTML responses when
UMAMI_WEBSITE_IDis set.
The script is injected before the closing </head> tag. Set the following environment variables to enable:
| Variable | Description | Default |
|---|---|---|
UMAMI_WEBSITE_ID |
Your Umami website ID (required to enable tracking). | '' (disabled) |
UMAMI_SRC |
URL to the Umami tracking script. | https://umami.internetguru.io/script.js |
UMAMI_IDENTIFY |
Send user identity data (id, user_type, optional user_role) on page load. |
true |
UMAMI_IDENTIFY_HASH |
Hash the user ID with SHA-256 before sending. | false |
Example .env:
UMAMI_WEBSITE_ID=0d38f931-afdc-4a99-a913-5c601fc95629
The injected script:
<script defer src="https://umami.internetguru.io/script.js" data-website-id="0d38f931-afdc-4a99-a913-5c601fc95629"></script>
PreventDuplicateSubmissions Middleware
Prevents duplicate POST form submissions by caching a hashed request fingerprint (IP + path + input minus reCAPTCHA) for 1 minute. Livewire update requests are excluded.
When a duplicate submission is detected, the user is redirected back with input and an error message.
SetPrevPage Middleware
Tracks the current and previous page URLs in the session for GET requests. Used internally by the exception handler to redirect users back to meaningful pages on errors.
Ignores AJAX requests and image (img/*) requests. Prevents tracking the same URL consecutively.
TimezoneMiddleware
Detects the user's timezone via IP geolocation and stores it in the session as
display_timezone.
Uses the GeolocationService to resolve the IP address. Falls back to config('geoip.default_location.timezone') on failure. Resolves only once per session.
VerifyCsrfToken
Extends Laravel's CSRF verification with HMAC-based request signature verification. Requests containing a valid
X-SignatureandX-Timestampheader pair bypass CSRF checks. Livewire routes are also excluded by default.
The signature is validated using the app key with a 60-second freshness window.
Helper Methods
The
Helpersclass provides useful static methods for Laravel applications.
Configuration and example usage:
-
Add the following lines to
config/app.php:use Illuminate\Support\Facades\Facade; 'aliases' => Facade::defaultAliases()->merge([ 'Helpers' => InternetGuru\LaravelCommon\Support\Helpers::class, ])->toArray(),
-
Use
Helpersclass methods in your application:<meta name="generator" content="{{ Helpers::getAppInfo() }}"/>
Available methods:
| Method | Description |
|---|---|
getAppInfoArray() |
Returns app name, environment, version, git branch, and commit as an array. |
getAppInfo() |
Returns app info as a single string. |
parseUrlPath($homeRoute, $skipFirst) |
Parses the current URL path into breadcrumb segments with translations. |
createTitle($separator, $homeRoute) |
Generates a page title from breadcrumb segments (reversed, separated). |
getEmailClientLink() |
Returns a link to the Mailpit inbox when using Mailpit mailer. |
verifyRequestSignature(Request $request) |
Verifies HMAC-SHA256 request signature (X-Signature + X-Timestamp headers). |
For full implementation details, see the Helpers class.
Helper Macros
The package registers a set of useful macros for
Str,Carbon, andNumber. See macros.php for the complete list.
String Macros
| Macro | Description |
|---|---|
Str::ref($length) |
Generates a random alphanumeric reference code (excludes ambiguous characters i, l, o, 0, 1, u). Starts with a letter and contains at least one digit. |
Number Macros
| Macro | Description |
|---|---|
Number::currencyForHumans($number, $in, $precision) |
Formats a number as a locale-aware currency string. Returns the currency symbol if no number is provided. |
Number::formatCurrencyToInput($number, $in, $precision, $inputTemplate) |
Formats a number for use inside an input field with a currency symbol. |
Carbon Macros
| Macro | Description |
|---|---|
$date->dateForHumans() |
Locale-aware date (L format). |
$date->dateTimeForHumans() |
Locale-aware date and time (L LT format). |
$date->myDiffForHumans() |
Human-readable time difference with "just now" for <60 seconds and "1 year" normalization. |
$date->timeForHumans() |
Clean time format (removes leading zeros and :00). |
$date->toDisplayTimezone() |
Converts the date to the user's display timezone stored in the session (display_timezone), falling back to config('app.timezone'). |
$date->randomWorkTime($from, $to) |
Sets a random time during work hours (default 9–17). |
Example usage:
use Carbon\Carbon; use Illuminate\Support\Facades\Number; use Illuminate\Support\Str; echo Str::ref(6); // Output: "k3mhpq" Number::useCurrency('USD'); echo Number::currencyForHumans(1234); // Output (en_US locale): $1,234 echo Number::currencyForHumans(); // Output (en_US locale): $ echo Number::currencyForHumans(1234.567, in: 'EUR', precision: 2); // Output (en_US locale): €1,234.57 $date = Carbon::parse('2023-12-31'); echo $date->dateForHumans(); // Output (en_US locale): 12/31/2023 $dateTime = Carbon::parse('2023-12-31 18:30:00'); echo $dateTime->dateTimeForHumans(); // Output (en_US locale): 12/31/2023 6:30 PM
Blade Components
All Blade components are registered under the ig namespace and can be used with <x-ig::component-name />.
Breadcrumb Blade Component
Renders breadcrumb navigation based on routes matching the current URL segments. Supports translations with short and long labels, custom divider, and segment skipping.
Key Features:
-
Customizable Divider – Allows a custom divider symbol between breadcrumb items.
-
Short and Long Labels – Using
trans_choiceif available shows n-th right translation based on the item position. -
Segment Skipping – Skips a specified number of URL segments. Useful for nested routes or routes with prefixes (e.g. language).
-
Skip Prefixes – Routes whose URI starts with a configured prefix are treated as error pages (no breadcrumb generated), preventing missing translation warnings. Configured via
breadcrumb_skip_prefixesinconfig/ig-common.php:'breadcrumb_skip_prefixes' => [ '_debugbar', '_ignition', 'livewire', 'storage', 'telescope', 'horizon', ],
Usage:
<!-- By default, this will generate breadcrumb items based on the current URL path. --> <x-ig::breadcrumb/> <!-- You can change the divider symbol by setting the divider attribute --> <x-ig::breadcrumb divider="|" /> <!-- If you need to skip certain segments of the URL (e.g., a language prefix), use the skipFirst attribute --> <x-ig::breadcrumb :skipFirst="1" />
Example:
- Assuming you have the following routes defined:
<?php Route::get('/', function () { // ... })->name('home'); Route::get('/products', function () { // ... })->name('products.index'); Route::get('/products/{product}', function ($product) { // ... })->name('products.show');
- And your translation files (
resources/lang/en/navig.php) include:<?php return [ 'home' => 'Long Application Name|LAN', 'products.index' => 'All Products|Products', 'products.show' => 'Product Details', ];
- When you visit the
/products/123URL, the short translation will be used for thehomeandproducts.indexroutes.LAN > Products > Product Details - When you visit the
/productsURL, the short label will be used for thehomeroute.LAN > All Products - When you visit the
/URL, the long label will be used for thehomeroute.Long Application Name
System Messages (Livewire Component)
Renders system temporary success messages and persistent error messages in different colors, with a close button. Powered by Livewire.
The component automatically picks up session success and errors data. You can also send messages dynamically via Livewire events.
Include the component in your Blade template:
<livewire:ig-messages />
Dispatching messages from other Livewire components:
$this->dispatch('ig-message', type: 'success', message: 'Item saved!'); $this->dispatch('ig-message', type: 'danger', message: 'Something went wrong.');
Form Blade Components
The package provides a set of Blade components for forms and various input types.
Notes:
- The Google reCAPTCHA V3 service is enabled by default. To disable it, set the
recaptchaattribute tofalse.
Complete example:
<x-ig::form action="route('test')" :recaptcha="false"> <x-ig::input type="text" name="name" required>Name</x-ig::input> <x-ig::input type="option" name="simple-options" :value="['a', 'b', 'c']">Simple Options</x-ig::input> <x-ig::input type="option" name="advanced-options" :value="[ ['id' => '1', 'value' => 'User 1' ], ['id' => '2', 'value' => 'User 2' ], ['id' => '3', 'value' => 'User 3' ], ]">Advanced Options</x-ig::input> <x-ig::input type="checkbox" name="checkbox" value="1">Checkbox</x-ig::input> <x-ig::input type="radio" name="radio" value="1">Radio</x-ig::input> <x-ig::input type="textarea" name="description">Description</x-ig::input> <x-ig::submit>Submit Form</x-ig::submit> </x-ig::form>
Language Switch Blade Component
Renders a language switcher as a list of links with the current language highlighted.
<x-ig::lang-switch />
Print Button Blade Component
Renders a print button that triggers the browser's print dialog.
<x-ig::print-button />
Footer Copy Blade Component
Renders a copyright footer with provider information and year range.
<x-ig::footer-copy /> <x-ig::footer-copy icon="" /> <x-ig::footer-copy icon="fa-solid fa-fw fa-leaf" />
| Prop | Default | Description |
|---|---|---|
icon |
null |
null renders the bundled duotone seedling SVG, an empty string omits the icon, any other value is used as an icon class. The icon is coloured by the provider-ico class ($provider-ico-color). |
Footer Blade Component
Renders the default application footer: page QR code, copy link, complaints form link, technical feedback form link, language switch and copyright.
<x-ig::footer />
Both feedback forms are laravel-feedback Livewire components declared by the footer itself, with the ids feedback-form and complaints-form. The technical feedback form is rendered whenever that package is installed and goes to the provider address; the complaints form additionally requires complaints-email to be set. Without the package the footer silently drops both forms and their links, everything else is rendered as usual.
Anything passed to the slot is rendered at the top of the footer, above the links. The component's own content is wrapped in a single div, so footer > div can be styled uniformly when the slot passes block elements too.
<x-ig::footer complaints-email="restaurant@example.com" :complaints-locations="['Restaurant Downtown', 'Restaurant Airport']" > <ul class="list-inline"> <li class="list-inline-item"><a href="https://www.facebook.com/example">Facebook</a></li> </ul> </x-ig::footer>
| Prop | Default | Description |
|---|---|---|
feedback-email |
ig-common::layouts.provider.email |
Technical feedback recipient. |
feedback-name |
ig-common::layouts.provider.name |
Technical feedback recipient name. |
feedback-title |
ig-common::layouts.support.link |
Link text and modal title. |
feedback-subject |
ig-common::layouts.support.subject |
Email subject. |
feedback-description |
null |
Modal description, falls back to the feedback package default. |
feedback-fields |
message, attachments, email |
Technical feedback field definitions, overrides the defaults entirely. |
complaints-email |
null |
Complaints recipient; the form is omitted when not set. |
complaints-name |
config('app.name') |
Complaints recipient name. |
complaints-title |
ig-common::layouts.complaints.link |
Link text and modal title. |
complaints-subject |
app name + title | Email subject. |
complaints-description |
null |
Modal description. |
complaints-fields |
location, occurred_at, message, email |
Feedback field definitions, overrides the defaults entirely. |
complaints-locations |
[] |
Location names rendered as a required select; the field is omitted when empty. |
feedback-icon |
none | Optional technical feedback link icon class. |
complaints-icon |
none | Optional complaints link icon class. |
share |
true |
Render the share page link. |
lang-switch |
true |
Render the language switch. |
generated |
false |
Render the page generation time in the display timezone. |
The location and occurred_at field definitions are registered into ig-feedback.names when laravel-feedback is installed, unless the application already defines them.
The technical feedback form declares an optional attachments field, so users can attach screenshots of what went wrong (up to 3 images or PDFs, 5 MB each). Limits and accepted types are configurable under ig-feedback.names.attachments, and feedback-fields replaces the field set altogether.
Modal Blade Component
Renders a Bootstrap modal whose markup is on the page from the start and only hidden, so it can be shown without a server round trip.
<x-ig::modal id="my-modal" title="My title">Body</x-ig::modal> <a href="javascript:void(0)" onclick="window.igModal.open('my-modal')">Open</a>
| Prop | Default | Description |
|---|---|---|
id |
slug of the title | Wrapper id, passed to window.igModal to open and close it. |
title |
null |
Modal title, next to the close button. |
open |
false |
Render it visible right away. |
centered |
false |
Vertically centre the dialog. |
hash |
null |
URL fragment opening the modal on page load; the fragment then follows it. |
wire-open |
null |
Livewire property mirroring the open state, kept in sync so a re-render does not close the modal. |
Additional attributes are merged onto the wrapper. The wrapper carries the ig-modal class and is hidden with d-none; window.igModal.open(id), close(id) and closeAll() toggle it, the escape key and a click on the backdrop close it. Every modal registers itself and is watched for class changes, so a Livewire re-render that opens or closes it is handled the same way as a click; opening and closing emit the bubbling ig-modal-opened and ig-modal-closed events. The helper is a small inline script, free of Alpine.js and Livewire, so the modal responds to the first click without waiting for a bundle to load.
Share Page Blade Component
Renders a link that opens a modal with a QR code of the current page URL, a copy button and the full URL below it, so the page can be opened on a phone or passed on.
<x-ig::share-page /> <x-ig::share-page url="https://example.com/menu" title="Share the menu" :size="320">Menu QR</x-ig::share-page>
| Prop | Default | Description |
|---|---|---|
url |
current URL | Encoded and shared content. |
title |
ig-common::layouts.share.title |
Modal title. |
icon |
fa-regular fa-fw fa-share-from-square |
Link icon class, pass an empty string to omit. |
size |
240 |
SVG size in pixels. |
id |
slug of the title | Modal id, also the URL fragment opening it. |
Links with an icon get the link-ico class, which positions the icon outside the link box so the link underline runs under the text only. The QR code is rendered server-side as an inline SVG, and the modal is the modal component, so it opens on the first click without waiting for a bundle to load, and linking to #share-page opens it on page load. The copy URL button copies the URL to the clipboard, and the URL below it wraps over as many lines as it needs.
Copy URL Blade Component
Renders a link that copies the current page URL to the clipboard, confirming with a check icon for two seconds.
<x-ig::copy-url /> <x-ig::copy-url url="https://example.com/menu">Copy the menu link</x-ig::copy-url>
| Prop | Default | Description |
|---|---|---|
url |
current URL | Copied value and href fallback. |
icon |
fa-regular fa-fw fa-copy |
Idle icon class, pass an empty string to omit both icons. |
copied-icon |
fa-solid fa-fw fa-check |
Icon shown for two seconds after copying. |
Clipboard access requires a secure context (HTTPS or localhost); when it is unavailable the failure is reported as an error message.
Card Blade Component
Renders a card: an optional row of chips, a heading, a subtitle and free content, optionally turning its whole surface into a link.
<x-ig::card title="Reduced balls and dual graphs" subtitle="Prague, 2026"> <p>Content of the card.</p> </x-ig::card> <x-ig::card title="Higher-dimensional chordality" badge="Preprint" badge-type="preprint" link="https://example.com/paper" link-label="Read the paper" /> <x-ig::card title="What we do" gray />
| Prop | Default | Description |
|---|---|---|
title |
– | Card heading. |
level |
4 |
Heading level of the title, so a card keeps the outline of the page it sits on. |
subtitle |
– | A line of context below the heading, rendered as p.lead. |
badge |
– | Text of the chip above the heading, or an array of several. |
badge-type |
– | Kind of chip, added as the badge-{type} class, which colours its dot. |
link |
– | Makes the whole card follow this link. |
link-label |
ig-common::layouts.card.open |
Accessible name of the link, since the button itself is only an icon. |
icon |
fa-solid fa-arrow-up-right-from-square |
Icon of the corner link button. |
gray |
false |
Puts the card on a grey surface, with its heading and content centred. |
Styles come from ig::common/card. The dot colour per badge kind is taken from the $card-badge-dots map, keyed by badge-type, which is empty by default:
$card-badge-dots: ( preprint: $orange, article: $green, thesis: $indigo, ); @import 'ig::common/card';
Two content classes are styled for use inside the slot: card-image for a full-bleed picture reaching over the card's padding, and card-list for a list of what the card holds. An actions element is pushed to the foot of the card, so buttons line up across cards of unequal height.
Card Row Blade Component
Groups cards into a sideways scrolling carousel with paging arrows, or into a grid wrapping onto as many lines as needed.
<x-ig::card-row label="Publications"> <x-ig::card title="First" /> <x-ig::card title="Second" /> </x-ig::card-row> <x-ig::card-row label="Research directions" layout="grid" size="narrow" tinted centered> <x-ig::card title="First" /> </x-ig::card-row>
| Prop | Default | Description |
|---|---|---|
label |
ig-common::layouts.card_row.label |
Accessible name of the group of cards. |
layout |
carousel |
carousel scrolls sideways with arrows, grid wraps onto as many lines as needed. |
size |
– | In the grid layout, narrow fits four cards to a line and wide two; omit for three. |
tinted |
false |
Tints each card with a colour derived from the label. |
centered |
false |
Centres the cards instead of pinning them to the left edge. |
Styles come from ig::common/card-row; the carousel needs the cardRow Alpine.js component, which ig::common-js registers. The row's gap, the widths its cards settle at and the look of the paging buttons are set by the $card-row-* variables, which have to be given before the stylesheet is imported. A carousel scrolls sideways, so its track clips downwards too — $card-row-bleed is the room the cards' shadows get inside it, and a card with a deeper shadow than the default needs it raised.
The tints are derived from the label, so a row keeps the same palette across requests while different rows get different colours. The palette repeats every eight cards, and neighbouring cards - including the wrap from the eighth back to the first - always sit about 135 degrees apart on the colour wheel.
Tag Cloud Blade Component
Shows a list of terms as coloured chips, or as a typographic word cloud whose lines are fitted to the width they have.
<x-ig::tag-cloud :tags="['Research', 'Teaching', 'Awards']" /> <x-ig::tag-cloud typography tags="Research, Teaching, Awards" />
| Prop | Default | Description |
|---|---|---|
tags |
[] |
The terms to show, as a list of strings or a comma separated string. |
typography |
false |
Draws the terms as plain coloured words of varying size, not as chips. |
Hues are spread by the golden angle, so neighbouring tags never land on a similar colour however many there are.
Styles come from ig::common/tag-cloud; the look of the chips and of the word cloud is set by the $tag-* and $tag-cloud-* variables, which have to be given before the stylesheet is imported. A cloud sitting on a card is usually given a negative inline margin through $tag-cloud-margin, so it reaches past the card's padding and reads as its own shape rather than as a block of text.
The typographic cloud needs the tagCloud Alpine.js component, which ig::common-js registers: its sizes are measured in the browser, since every line is packed and then scaled to fill the width of the cloud exactly. The cloud stays hidden until it has been measured, so the fallback sizes rendered server-side never show; without JavaScript those sizes are what is shown.
Demo Info Blade Component
Renders a demo mode warning banner informing users that displayed information is illustrative and may reset.
<x-ig::demo-info />
Read-Only Mode Info Blade Component
Renders an informational banner indicating the application is in read-only mode and editing is disabled.
<x-ig::read-only-mode-info />
Email Feedback Blade Component
Renders a technical support email link with pre-filled subject and diagnostic data.
<x-ig::email-feedback />
Editable Blade Component
Provides an Alpine.js
editabledata component for inline editing functionality.
<x-ig::editable />
Admin Button Text Blade Component
Renders a slot for button text that is replaced with a configurable admin label when the authenticated user is an admin.
<x-ig::admin-button-text>Save</x-ig::admin-button-text>
When auth()->user()->isAdmin() is true, the slot content is replaced with the ig-common::layouts.submit-admin translation string.
Association History Blade Component
Renders a chronological edit history for a model, grouped by author and 10-minute time windows. Requires the
AssociationHistorytrait on the model.
Setup:
-
Publish and run the migration:
php artisan vendor:publish --tag=ig-common:migrations php artisan migrate
-
Add the trait and declare which fields to track:
use InternetGuru\LaravelCommon\Traits\AssociationHistory; class Reservation extends Model { use AssociationHistory; protected array $associationHistoryTracked = ['status', 'note']; }
-
Optionally configure column label and label value translations in
config/ig-common.php:'association_history' => [ 'columns' => [ \App\Models\Reservation::class => 'reservation.history.column', ], ],
With corresponding translation keys like
reservation.history.column.status,reservation.history.column.status.pending,reservation.history.column.status.confirmedandreservation.history.column.note. -
Render the component:
<x-ig::association-history :model="$reservation" />
Optional
:limitattribute (default10) controls how many entries are loaded.
The component derives the "new value" for each entry from the current model state and automatically reads the created_by field (override via $associationHistoryCreatedBy) to prepend a creation entry.
Component Signatures Blade Component
Renders an HTML comment listing all installed
internetguru/*package names and versions. Only active in debug mode.
<x-ig::component-signatures />
Casts
CarbonIntervalCast
Casts a string to a
CarbonIntervaland back. UsesCarbonInterval::fromString()to parse andforHumans()(in English locale) to serialize.
use Illuminate\Database\Eloquent\Model; use InternetGuru\LaravelCommon\Casts\CarbonIntervalCast; class Task extends Model { protected $casts = [ 'duration' => CarbonIntervalCast::class, ]; }
Traits
Ulid32 Trait
Provides ULID (Crockford Base32) utility methods for Eloquent models. Generates 26-character Base32-encoded UUIDs and adds human-readable formatting, URL generation, and link rendering.
use InternetGuru\LaravelCommon\Traits\Ulid32; class Order extends Model { use Ulid32; }
Available methods:
| Method | Description |
|---|---|
$model->ulidForHumans() |
Formats the ULID with dashes (e.g. 01JM-ABCDEF-GHIJKL-MNOPQR-STUV). |
$model->shortUlidForHumans() |
Returns the last 7 characters of the formatted ULID. |
$model->ulidUrl($usp) |
Generates a URL to the model's show route. |
$model->ulidLink($content) |
Renders an HTML link to the model. |
Model::generateBase32Uuid() |
Generates a new Crockford Base32-encoded UUID (26 characters). |
Rules
Ulid32 Validation Rule
Validates that a value is a valid 26-character Crockford Base32 ULID (no
I,L,O,Ucharacters).
Can be used as a class-based rule or via the globally registered ulid32 rule:
// Class-based use InternetGuru\LaravelCommon\Rules\Ulid32; $request->validate([ 'code' => ['required', new Ulid32], ]); // String-based (registered globally by CommonServiceProvider) $request->validate([ 'code' => 'required|ulid32', ]);
Services
GeolocationService
Resolves an IP address to a geographic location using the
torann/geoippackage. Results are cached and rate-limited (5 lookups per 60 seconds).
use InternetGuru\LaravelCommon\Services\GeolocationService; $geoService = app(GeolocationService::class); $location = $geoService->getLocation('8.8.8.8'); echo $location->timezone; // "America/Chicago" echo $location->country; // "US"
Throws GeolocationServiceException on failure or rate limit.
Notifications
BaseNotification
Abstract queued notification class that captures request context (IP, timezone, user ID, URL) at creation time and sends via the
Features:
- Queued with 10 retries and 2-minute backoff.
- Retry middleware via
LogNotificationFailure— logs warnings on transient failures. - Captures the sender's IP, timezone (via geolocation), authenticated user ID, and current page URL.
- Logs permanently failed notifications.
Extend this class to create your own notifications:
use InternetGuru\LaravelCommon\Notifications\BaseNotification; class OrderConfirmation extends BaseNotification { public function toMail(object $notifiable): MailMessage { return (new \InternetGuru\LaravelCommon\Mail\MailMessage) ->setExtraMailData($this->getExtraMailData()) ->subject('Order Confirmed') ->view(['html' => 'emails.order-confirmed', 'text' => 'emails.order-confirmed-text']); } }
MailMessage
Extends Laravel's
MailMessagewith automatic reference number generation, no-reply detection, and extra mail data injection.
Features:
- Appends a random reference code (
Ref XXXXX) to every subject line for tracking. - Automatically detects no-reply addresses and adds a "replies not delivered" note.
- Supports arbitrary extra data (
setExtraMailData) passed to email views (IP, timezone, user ID, etc.).
Available methods:
| Method | Description |
|---|---|
->to($address, $name) |
Set one or multiple recipients. Accepts a single address or an array of [email => name] pairs. |
->withoutRefNumber() |
Suppress the Ref XXXXX suffix from the subject and footer. |
->setRefNumber($ref) |
Override the auto-generated reference code. |
->setExtraMailData($data) |
Merge additional data passed to the email view. |
Mail Logging
All sent mail notifications are automatically logged to the
mail_logsdatabase table via theLogSentNotificationlistener.
Publish the migration to create the mail_logs table:
php artisan vendor:publish --tag=ig-common:migrations php artisan migrate
Logged fields: to, replyto, subject, body, created_at, updated_at.
Exception Handling
The package registers a custom exception handler that provides user-friendly error pages and JSON responses for common HTTP errors (401, 402, 403, 404, 419, 429, 500, 503).
Features:
- Read-only mode:
DbReadOnlyExceptionreturns a 503 response (JSON or redirect with error). - Connection errors:
ConnectExceptionreturns a friendly error message. - Rate limiting (429) and session expiration (419): Handled with translated messages.
- Debug mode: Uses
dd()for detailed exception inspection. - JSON support: Returns JSON responses when the request expects JSON.
- Redirects to the previously tracked page (via
SetPrevPagemiddleware) on error. - 503 auto-refresh: The 503 error page automatically reloads after 30 seconds.
Custom error views are included for standard HTTP status codes. The error pages use the ig-common::layouts.base layout.
Logging
JsonDailyLogger
A custom Monolog logger that writes JSON-formatted daily rotating log files enriched with request context.
Each log entry includes:
- User info: ID, name, email (if authenticated).
- Request input: All inputs except
password,_token, andg-recaptcha-response. Livewire snapshot data is stripped. - Session ID.
- App info: Git branch and commit.
- Request info: URL, IP, HTTP method, server, referrer, user agent.
Configuration in config/logging.php:
'channels' => [ 'json_daily' => [ 'driver' => 'custom', 'via' => InternetGuru\LaravelCommon\Logging\JsonDailyLogger::class, 'path' => storage_path('logs/app.log'), 'days' => 14, 'level' => 'debug', ], ],
Localization
The package includes translations in English (en), Czech (cs), and Danish (da) for:
- Error pages (
errors.php) – HTTP status messages and descriptions. - Layout strings (
layouts.php) – Footer, email templates, support links, and UI labels. - System messages (
messages.php) – Validation messages, email labels, and demo mode warnings. - Navigation (
navig.php) – Breadcrumb labels for routes.
Publishing Assets
You can publish package assets to customize them:
# Publish database migrations php artisan vendor:publish --tag=ig-common:migrations # Publish configuration php artisan vendor:publish --tag=ig-common:config # Publish views for customization php artisan vendor:publish --tag=ig-common:views # Publish language files php artisan vendor:publish --tag=ig-common:lang
License & Commercial Terms
License
Copyright © 2026 Internet Guru
This software is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) license.
Disclaimer: This software is provided "as is", without warranty of any kind, express or implied. In no event shall the authors or copyright holders be liable for any claim, damages or other liability.
Commercial Use
The standard CC BY-NC-SA license prohibits commercial use. If you wish to use this software in a commercial environment or product, we offer flexible commercial licenses tailored to:
- Your company size.
- The nature of your project.
- Your specific integration needs.
Note: In many instances (especially for startups or small-scale tools), this may result in no fees being charged at all. Please contact us to obtain written permission or a commercial agreement.
Contact for Licensing: info@internetguru.io
Professional Services
Are you looking to get the most out of this project? We are available for:
- Custom Development: Tailoring the software to your specific requirements.
- Integration & Support: Helping your team implement and maintain the solution.
- Training & Workshops: Seminars and hands-on workshops for your developers.
Reach out to us at info@internetguru.io — we are more than happy to assist you!