Deploy Ladill QR Plus / deploy (push) Successful in 1m6s
Let accounts organise codes by promo or launch, attach or detach codes, and view combined scan totals from the sidebar.
223 lines
5.7 KiB
PHP
223 lines
5.7 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Support\LadillLink;
|
|
use App\Support\Qr\QrStyleDefaults;
|
|
use App\Support\Qr\QrTypeCatalog;
|
|
use App\Support\Qr\QrWifiPayload;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
class QrCode extends Model
|
|
{
|
|
public const TYPE_URL = 'url';
|
|
public const TYPE_DOCUMENT = 'document';
|
|
public const TYPE_LINK_LIST = 'link_list';
|
|
public const TYPE_VCARD = 'vcard';
|
|
public const TYPE_BUSINESS = 'business';
|
|
public const TYPE_IMAGE = 'image';
|
|
public const TYPE_CHURCH = 'church';
|
|
public const TYPE_MENU = 'menu';
|
|
public const TYPE_SHOP = 'shop';
|
|
public const TYPE_APP = 'app';
|
|
public const TYPE_BOOK = 'book';
|
|
public const TYPE_WIFI = 'wifi';
|
|
public const TYPE_COUPON = 'coupon';
|
|
public const TYPE_EVENT = 'event';
|
|
public const TYPE_ITINERARY = 'itinerary';
|
|
|
|
protected $fillable = [
|
|
'user_id',
|
|
'campaign_id',
|
|
'short_code',
|
|
'type',
|
|
'label',
|
|
'destination_url',
|
|
'qr_document_id',
|
|
'payload',
|
|
'is_active',
|
|
'png_path',
|
|
'svg_path',
|
|
'scans_total',
|
|
'unique_scans_total',
|
|
'last_scanned_at',
|
|
'destination_updated_at',
|
|
];
|
|
|
|
protected $casts = [
|
|
'payload' => 'array',
|
|
'is_active' => 'boolean',
|
|
'scans_total' => 'integer',
|
|
'unique_scans_total' => 'integer',
|
|
'last_scanned_at' => 'datetime',
|
|
'destination_updated_at' => 'datetime',
|
|
];
|
|
|
|
public function user(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class);
|
|
}
|
|
|
|
public function campaign(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Campaign::class);
|
|
}
|
|
|
|
public function document(): BelongsTo
|
|
{
|
|
return $this->belongsTo(QrDocument::class, 'qr_document_id');
|
|
}
|
|
|
|
public function scanEvents(): HasMany
|
|
{
|
|
return $this->hasMany(QrScanEvent::class);
|
|
}
|
|
|
|
public function transactions(): HasMany
|
|
{
|
|
return $this->hasMany(QrTransaction::class);
|
|
}
|
|
|
|
public function saleOrders(): HasMany
|
|
{
|
|
return $this->hasMany(QrSaleOrder::class);
|
|
}
|
|
|
|
public function eventRegistrations(): HasMany
|
|
{
|
|
return $this->hasMany(QrEventRegistration::class);
|
|
}
|
|
|
|
public function publicUrl(): string
|
|
{
|
|
return LadillLink::url($this->short_code);
|
|
}
|
|
|
|
public function publicPath(string $suffix = ''): string
|
|
{
|
|
return $suffix === ''
|
|
? $this->publicUrl()
|
|
: LadillLink::path($this->short_code, $suffix);
|
|
}
|
|
|
|
public static function publicBaseUrl(): string
|
|
{
|
|
return LadillLink::baseUrl();
|
|
}
|
|
|
|
/**
|
|
* String encoded inside the QR image.
|
|
*
|
|
* Most types encode the stable short link so the printed code never changes
|
|
* when content is edited. WiFi is the exception: it bakes the network
|
|
* credentials directly into the image so devices auto-join on scan. That
|
|
* payload is frozen at creation (payload.wifi_encoded) — editing the network
|
|
* afterwards updates the saved info but never re-encodes the printed code.
|
|
*/
|
|
public function encodedPayload(): string
|
|
{
|
|
if ($this->type === self::TYPE_WIFI) {
|
|
$frozen = $this->payload['wifi_encoded'] ?? null;
|
|
|
|
return is_string($frozen) && $frozen !== ''
|
|
? $frozen
|
|
: QrWifiPayload::encode($this->content());
|
|
}
|
|
|
|
return $this->publicUrl();
|
|
}
|
|
|
|
/** WiFi codes encode their join payload directly (auto-join), not a redirect link. */
|
|
public function encodesDirectPayload(): bool
|
|
{
|
|
return $this->type === self::TYPE_WIFI;
|
|
}
|
|
|
|
public function typeLabel(): string
|
|
{
|
|
return QrTypeCatalog::label($this->type);
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
public function content(): array
|
|
{
|
|
return (array) ($this->payload['content'] ?? []);
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
public function style(): array
|
|
{
|
|
return QrStyleDefaults::merge($this->payload['style'] ?? null);
|
|
}
|
|
|
|
public function isUrlType(): bool
|
|
{
|
|
return $this->type === self::TYPE_URL;
|
|
}
|
|
|
|
public function isDocumentType(): bool
|
|
{
|
|
return $this->type === self::TYPE_DOCUMENT;
|
|
}
|
|
|
|
public function isImageType(): bool
|
|
{
|
|
return $this->type === self::TYPE_IMAGE;
|
|
}
|
|
|
|
public function isMenuType(): bool
|
|
{
|
|
return $this->type === self::TYPE_MENU;
|
|
}
|
|
|
|
public function isShopType(): bool
|
|
{
|
|
return $this->type === self::TYPE_SHOP;
|
|
}
|
|
|
|
public function isBookType(): bool
|
|
{
|
|
return $this->type === self::TYPE_BOOK;
|
|
}
|
|
|
|
public function acceptsOrders(): bool
|
|
{
|
|
return in_array($this->type, [self::TYPE_MENU, self::TYPE_SHOP, self::TYPE_CHURCH, self::TYPE_EVENT], true);
|
|
}
|
|
|
|
public function usesLandingPage(): bool
|
|
{
|
|
return in_array($this->type, [
|
|
self::TYPE_DOCUMENT,
|
|
self::TYPE_LINK_LIST,
|
|
self::TYPE_VCARD,
|
|
self::TYPE_BUSINESS,
|
|
self::TYPE_CHURCH,
|
|
self::TYPE_IMAGE,
|
|
self::TYPE_MENU,
|
|
self::TYPE_SHOP,
|
|
self::TYPE_APP,
|
|
self::TYPE_BOOK,
|
|
self::TYPE_WIFI,
|
|
self::TYPE_EVENT,
|
|
self::TYPE_ITINERARY,
|
|
], true);
|
|
}
|
|
|
|
public function resolvesToRedirect(): bool
|
|
{
|
|
return $this->type === self::TYPE_URL;
|
|
}
|
|
|
|
public function redirectUrl(): ?string
|
|
{
|
|
if ($this->destination_url) {
|
|
return $this->destination_url;
|
|
}
|
|
|
|
return $this->content()['url'] ?? null;
|
|
}
|
|
}
|