diff --git a/.github/workflows/integrate.yml b/.github/workflows/integrate.yml index 499a5d3..c014f30 100644 --- a/.github/workflows/integrate.yml +++ b/.github/workflows/integrate.yml @@ -16,7 +16,7 @@ jobs: - name: "Check file permissions" run: | - test "$(find . -type f -not -path './.git/*' -executable)" == "" + test "$(find . -type f -not -path './.git/*' -not -wholename './link' -executable)" == "" - name: "Find non-printable ASCII characters" run: | diff --git a/assets/package.json b/assets/package.json index 013a877..58a9e7e 100644 --- a/assets/package.json +++ b/assets/package.json @@ -5,6 +5,27 @@ "version": "1.0.0", "symfony": { "controllers": { + "backgroundsync-form": { + "main": "src/backgroundsync-form_controller.js", + "name": "pwa/backgroundsync-form", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, + "badge": { + "main": "src/badge_controller.js", + "name": "pwa/badge", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, + "battery": { + "main": "src/battery_controller.js", + "name": "pwa/battery", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, "connection-status": { "main": "src/connection-status_controller.js", "name": "pwa/connection-status", @@ -12,16 +33,30 @@ "fetch": "eager", "enabled": true }, - "backgroundsync-form": { - "main": "src/backgroundsync-form_controller.js", - "name": "pwa/backgroundsync-form", + "device-orientation": { + "main": "src/device-orientation_controller.js", + "name": "pwa/device-orientation", "webpackMode": "eager", "fetch": "eager", "enabled": true }, - "sync-broadcast": { - "main": "src/sync-broadcast_controller.js", - "name": "pwa/sync-broadcast", + "fullscreen": { + "main": "src/fullscreen_controller.js", + "name": "pwa/fullscreen", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, + "geolocation": { + "main": "src/geolocation_controller.js", + "name": "pwa/geolocation", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, + "install": { + "main": "src/install_controller.js", + "name": "pwa/install", "webpackMode": "eager", "fetch": "eager", "enabled": true @@ -32,6 +67,41 @@ "webpackMode": "eager", "fetch": "eager", "enabled": true + }, + "presentation": { + "main": "src/presentation_controller.js", + "name": "pwa/presentation", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, + "receiver": { + "main": "src/receiver_controller.js", + "name": "pwa/receiver", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, + "share": { + "main": "src/share_controller.js", + "name": "pwa/share", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, + "sync-broadcast": { + "main": "src/sync-broadcast_controller.js", + "name": "pwa/sync-broadcast", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, + "vibration": { + "main": "src/vibration_controller.js", + "name": "pwa/vibration", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true } }, "importmap": { diff --git a/assets/src/abstract_controller.js b/assets/src/abstract_controller.js new file mode 100644 index 0000000..bab060b --- /dev/null +++ b/assets/src/abstract_controller.js @@ -0,0 +1,10 @@ +'use strict'; + +import { Controller } from '@hotwired/stimulus'; + +/* stimulusFetch: 'lazy' */ +export default class extends Controller { + dispatchEvent = (name, payload) => { + this.dispatch(name, { detail: payload }); + } +} diff --git a/assets/src/backgroundsync-form_controller.js b/assets/src/backgroundsync-form_controller.js index 02a9874..ac47b58 100644 --- a/assets/src/backgroundsync-form_controller.js +++ b/assets/src/backgroundsync-form_controller.js @@ -5,13 +5,16 @@ import { Controller } from '@hotwired/stimulus'; /* stimulusFetch: 'lazy' */ export default class extends Controller { static values = { - params: { type: Object, default: { - mode: 'cors', - cache: 'no-cache', - credentials: 'same-origin', - redirect: 'follow', - referrerPolicy: 'no-referrer' - }}, + params: { + type: Object, + default: { + mode: 'cors', + cache: 'no-cache', + credentials: 'same-origin', + redirect: 'follow', + referrerPolicy: 'no-referrer' + } + }, headers: { type: Object, default: {} }, redirection: { type: String, default: null }, }; @@ -36,11 +39,11 @@ export default class extends Controller { params.headers['Content-Type'] = 'application/x-www-form-urlencoded'; params.body = new URLSearchParams(new FormData(form)); } else { - console.error('Unsupported form enctype'); + // Unsupported form enctype + return; } params.method = form.method.toUpperCase(); const response = await fetch(url, params); - console.log(new URLSearchParams(params.body).toString(), params, params.headers); if (response.redirected) { window.location.assign(response.url); return; diff --git a/assets/src/badge_controller.js b/assets/src/badge_controller.js new file mode 100644 index 0000000..efc3ab0 --- /dev/null +++ b/assets/src/badge_controller.js @@ -0,0 +1,16 @@ +'use strict'; + +import AbstractController from './abstract_controller.js'; + +/* stimulusFetch: 'lazy' */ +export default class extends AbstractController { + update = async ({counter}) => { + await navigator.setAppBadge(counter); + this.dispatchEvent('badge:updated', { counter }); + } + + clear = async () => { + await navigator.clearAppBadge(); + this.dispatchEvent('badge:cleared'); + } +} diff --git a/assets/src/battery_controller.js b/assets/src/battery_controller.js new file mode 100644 index 0000000..13e6c8d --- /dev/null +++ b/assets/src/battery_controller.js @@ -0,0 +1,36 @@ +'use strict'; + +import AbstractController from './abstract_controller.js'; + +/* stimulusFetch: 'lazy' */ +export default class extends AbstractController { + async connect() { + const battery = await navigator.getBattery(); + battery.addEventListener('chargingchange', () => this.updateChargeInfo(battery)); + battery.addEventListener('levelchange', () => this.updateLevelInfo(battery)); + battery.addEventListener('chargingtimechange', () => this.updateChargingInfo(battery)); + battery.addEventListener('dischargingtimechange', () => this.updateDischargingInfo(battery)); + + await this.updateChargeInfo(battery); + await this.updateLevelInfo(battery); + await this.updateChargingInfo(battery); + await this.updateDischargingInfo(battery); + } + update = async ({counter}) => { + await navigator.setAppBadge(counter); + this.dispatchEvent('badge:updated', { counter }); + } + + updateChargeInfo = async (battery) => { + this.dispatchEvent('battery:charge', { charging: battery.charging }); + } + updateLevelInfo = async (battery) => { + this.dispatchEvent('battery:level', { level: battery.level }); + } + updateChargingInfo = async (battery) => { + this.dispatchEvent('battery:chargingtime', { chargingTime: battery.chargingTime }); + } + updateDischargingInfo = async (battery) => { + this.dispatchEvent('battery:dischargingtime', { dischargingTime: battery.dischargingTime }); + } +} diff --git a/assets/src/connection-status_controller.js b/assets/src/connection-status_controller.js index a6ee963..ba28ba3 100644 --- a/assets/src/connection-status_controller.js +++ b/assets/src/connection-status_controller.js @@ -1,9 +1,9 @@ 'use strict'; -import { Controller } from '@hotwired/stimulus'; +import AbstractController from './abstract_controller.js'; /* stimulusFetch: 'lazy' */ -export default class extends Controller { +export default class extends AbstractController { static targets = ['message', 'attribute']; static values = { onlineMessage: { type: String, default: 'You are online.' }, @@ -37,9 +37,6 @@ export default class extends Controller { }); }); } - dispatchEvent = (name, payload) => { - this.dispatch(name, { detail: payload }); - } statusChanged = (data) => { this.messageTargets.forEach((element) => { diff --git a/assets/src/device-orientation_controller.js b/assets/src/device-orientation_controller.js new file mode 100644 index 0000000..3a90092 --- /dev/null +++ b/assets/src/device-orientation_controller.js @@ -0,0 +1,21 @@ +'use strict'; + +import AbstractController from './abstract_controller.js'; + +/* stimulusFetch: 'lazy' */ +export default class extends AbstractController { + connect() { + window.addEventListener( + 'deviceorientation', + (event) => { + this.dispatchEvent({ + absolute: event.absolute, + alpha: event.alpha, + beta: event.beta, + gamma: event.gamma + }) + }, + true + ); + } +} diff --git a/assets/src/fullscreen_controller.js b/assets/src/fullscreen_controller.js new file mode 100644 index 0000000..1fc2db9 --- /dev/null +++ b/assets/src/fullscreen_controller.js @@ -0,0 +1,39 @@ +'use strict'; + +import AbstractController from './abstract_controller.js'; + +/* stimulusFetch: 'lazy' */ +export default class extends AbstractController { + connect () { + document.addEventListener("fullscreenchange", () => { + this.dispatchEvent('fullscreen:change', { + fullscreen: document.fullscreenElement !== null, + element: document.fullscreenElement + }); + }); + document.addEventListener("fullscreenerror", () => { + this.dispatchEvent('fullscreen:error', { + element: document.fullscreenElement + }); + }); + } + + request = async (event) => { + const {params} = event; + const {target, ...rest} = params; + if (!target) { + await document.documentElement.requestFullscreen(rest); + return + } + const element = document.getElementById(target); + if (!element) { + console.error('Element not found:', target); + return; + } + await element.requestFullscreen(rest); + } + + exit = async () => { + await document.exitFullscreen(); + } +} diff --git a/assets/src/geolocation_controller.js b/assets/src/geolocation_controller.js new file mode 100644 index 0000000..540eb50 --- /dev/null +++ b/assets/src/geolocation_controller.js @@ -0,0 +1,47 @@ +'use strict'; + +import AbstractController from './abstract_controller.js'; + +/* stimulusFetch: 'lazy' */ +export default class extends AbstractController { + watchId = null; + + locate({params}) { + if (!navigator.geolocation) { + this.dispatchEvent('geolocation:unsupported'); + return; + } + + navigator.geolocation.getCurrentPosition( + (position) => {this.dispatchEvent('geolocation:position', {latitude: position.coords.latitude, longitude: position.coords.longitude});}, + (error) => {this.dispatchEvent('geolocation:error', {error: error});}, + params + ); + } + + watch({params}) { + if (!navigator.geolocation) { + this.dispatchEvent('geolocation:unsupported'); + return; + } + if (this.watchId) { + return; + } + + this.watchId = navigator.geolocation.watchPosition( + (position) => {this.dispatchEvent('geolocation:position', {latitude: position.coords.latitude, longitude: position.coords.longitude});}, + (error) => {this.dispatchEvent('geolocation:error', {error: error});}, + params + ); + } + + clearWatch() { + if (!this.watchId) { + return; + } + + navigator.geolocation.clearWatch(this.watchId); + this.watchId = null; + this.dispatchEvent('geolocation:watch:cleared'); + } +} diff --git a/assets/src/install_controller.js b/assets/src/install_controller.js new file mode 100644 index 0000000..87906b9 --- /dev/null +++ b/assets/src/install_controller.js @@ -0,0 +1,42 @@ +'use strict'; + +import AbstractController from './abstract_controller.js'; + +/* stimulusFetch: 'lazy' */ +export default class extends AbstractController { + static targets = ['install']; + installPrompt = null; + + async connect() { + this.disableInstallTargets(); + window.addEventListener("beforeinstallprompt", async (event) => { + event.preventDefault(); + this.installPrompt = event; + this.enableInstallTargets(); + }); + } + + async install() { + if (!this.installPrompt) { + return; + } + const result = await this.installPrompt.prompt(); + if (result.outcome === 'accepted') { + this.disableInstallTargets(); + } else { + this.dispatchEvent('install:cancelled'); + } + } + + enableInstallTargets() { + this.installTargets.forEach((installElement) => { + installElement.removeAttribute("hidden"); + }); + } + + disableInstallTargets() { + this.installTargets.forEach((installElement) => { + installElement.setAttribute("hidden", ""); + }); + } +} diff --git a/assets/src/prefetch-on-demand_controller.js b/assets/src/prefetch-on-demand_controller.js index f24d58e..e8e7d55 100644 --- a/assets/src/prefetch-on-demand_controller.js +++ b/assets/src/prefetch-on-demand_controller.js @@ -1,9 +1,9 @@ 'use strict'; -import { Controller } from '@hotwired/stimulus'; +import AbstractController from './abstract_controller.js'; /* stimulusFetch: 'lazy' */ -export default class extends Controller { +export default class extends AbstractController { prefetch = async ({params}) => { const workbox = window.workbox; if (!workbox || !params.urls) { @@ -21,8 +21,4 @@ export default class extends Controller { {params} ); } - - dispatchEvent = (name, payload) => { - this.dispatch(name, { detail: payload }); - } } diff --git a/assets/src/presentation_controller.js b/assets/src/presentation_controller.js new file mode 100644 index 0000000..cdccb54 --- /dev/null +++ b/assets/src/presentation_controller.js @@ -0,0 +1,69 @@ +'use strict'; + +import AbstractController from './abstract_controller.js'; + +/* stimulusFetch: 'lazy' */ +export default class extends AbstractController { + static values = { + urls: { type: Array }, + }; + + request = null; + connection = null; + async connect() { + this.request = new PresentationRequest(this.urlsValue); + const availability = await this.request.getAvailability(); + this.dispatchEvent('presentation:availability:changed', { availability }); + availability.onchange = () => { + this.dispatchEvent('presentation:availability:changed', { availability }); + } + } + + start = async () => { + if (!this.request) { + return; + } + const connection = await this.request.start(); + this.setConnection(connection); + } + + reconnect = async () => { + const connectionId = localStorage.getItem('presentation_connection_id'); + if (!connectionId) { + return; + } + + const connection = await this.request.reconnect(connectionId); + this.setConnection(connection); + } + + async send ({params}) { + if (!this.connection) { + return; + } + const {message} = params; + this.connection.send(message); + } + + terminate = () => { + if (!this.connection) { + return; + } + const id = this.connection.id; + this.connection.onclose = null; + this.connection.terminate(); + this.connection = null; + localStorage.removeItem('presentation_connection_id'); + this.dispatchEvent('presentation:terminated', {id}); + } + + setConnection(connection) { + if (this.connection) { + this.terminate(); + } + + this.connection = connection; + localStorage.setItem('presentation_connection_id', connection.id); + this.dispatchEvent('presentation:started', {id: connection.id}); + } +} diff --git a/assets/src/receiver_controller.js b/assets/src/receiver_controller.js new file mode 100644 index 0000000..934d5b6 --- /dev/null +++ b/assets/src/receiver_controller.js @@ -0,0 +1,27 @@ +'use strict'; + +import AbstractController from './abstract_controller.js'; + +/* stimulusFetch: 'lazy' */ +export default class extends AbstractController { + async connect() { + if (!navigator.presentation.receiver) { + } + const connections = await navigator.presentation.receiver.connections; + connections.connections.map(connection => addConnection(connection)); + connections.addEventListener( + 'connectionavailable', + (event) => { + const connection = event.connection; + connection.addEventListener( + 'message', + (event) => this.dispatchEvent('message', {message: event.data}) + ); + connection.addEventListener( + 'close', + () => this.dispatchEvent('close') + ); + } + ); + } +} diff --git a/assets/src/share_controller.js b/assets/src/share_controller.js new file mode 100644 index 0000000..80e0cdc --- /dev/null +++ b/assets/src/share_controller.js @@ -0,0 +1,24 @@ +'use strict'; + +import AbstractController from './abstract_controller.js'; + +/* stimulusFetch: 'lazy' */ +export default class extends AbstractController { + async share({params}) { + const {data} = params; + if (!data) { + console.error("No data provided"); + return; + } + try { + if (!navigator.canShare || !navigator.canShare(data)) { + console.error("Cannot share data"); + return; + } + await navigator.share(data); + this.dispatchEvent('share:success', {data}); + } catch (error) { + console.error("Error sharing", {error}); + } + } +} diff --git a/assets/src/sync-broadcast_controller.js b/assets/src/sync-broadcast_controller.js index fd7600c..19efde0 100644 --- a/assets/src/sync-broadcast_controller.js +++ b/assets/src/sync-broadcast_controller.js @@ -1,9 +1,9 @@ 'use strict'; -import { Controller } from '@hotwired/stimulus'; +import AbstractController from './abstract_controller.js'; /* stimulusFetch: 'lazy' */ -export default class extends Controller { +export default class extends AbstractController { static values = { channel: { type: String }, }; @@ -25,10 +25,6 @@ export default class extends Controller { } } - dispatchEvent = (name, payload) => { - this.dispatch(name, { detail: payload }); - } - messageReceived = async (event) => { const data = event.data; this.remainingTargets.forEach((element) => { diff --git a/assets/src/vibration_controller.js b/assets/src/vibration_controller.js new file mode 100644 index 0000000..034c748 --- /dev/null +++ b/assets/src/vibration_controller.js @@ -0,0 +1,45 @@ +'use strict'; + +import AbstractController from './abstract_controller.js'; + +/* stimulusFetch: 'lazy' */ +export default class extends AbstractController { + vibrateInterval = null; + + vibrate = async ({params}) => { + const { sequence } = params; + if (!sequence) { + console.error('Vibration sequence is required.'); + return; + } + await navigator.vibrate(sequence); + this.dispatchEvent('vibration:triggered', { sequence }); + } + + persistent = async ({params}) => { + if (this.vibrateInterval !== null) { + this.stop(); + } + const { sequence, duration } = params; + if (!sequence) { + console.error('Vibration sequence is required.'); + return; + } + if (!duration) { + console.error('Vibration duration is required.'); + return; + } + this.vibrateInterval = setInterval(() => { + startVibrate(sequence); + }, duration); + } + + stop = async () => { + if (this.vibrateInterval === null) { + return; + } + clearInterval(this.vibrateInterval); + this.vibrateInterval = null; + this.dispatchEvent('vibration:stopped'); + } +} diff --git a/castor.php b/castor.php index 5820b93..4beb2e6 100644 --- a/castor.php +++ b/castor.php @@ -1,9 +1,10 @@ 'coverage', - ]; - run($command, environment: $environment); + $context = context(); + $context = $context->withEnvironment(['XDEBUG_MODE' => 'coverage']); + run($command, context: $context); } #[AsTask(description: 'Run tests')] @@ -39,21 +39,20 @@ function test(bool $coverageHtml = false, bool $coverageText = false, null|strin { io()->title('Running tests'); $command = ['php', 'vendor/bin/phpunit', '--color']; - $environment = [ - 'XDEBUG_MODE' => 'off', - ]; + $context = context(); + $context = $context->withEnvironment(['XDEBUG_MODE' => 'off']); if ($coverageHtml) { $command[] = '--coverage-html=build/coverage'; - $environment['XDEBUG_MODE'] = 'coverage'; + $context = $context->withEnvironment(['XDEBUG_MODE' => 'coverage']); } if ($coverageText) { $command[] = '--coverage-text'; - $environment['XDEBUG_MODE'] = 'coverage'; + $context = $context->withEnvironment(['XDEBUG_MODE' => 'coverage']); } if ($group !== null) { $command[] = sprintf('--group=%s', $group); } - run($command, environment: $environment); + run($command, context: $context); } #[AsTask(description: 'Coding standards check')] @@ -65,16 +64,15 @@ function cs( ): void { io()->title('Running coding standards check'); $command = ['php', 'vendor/bin/ecs', 'check']; - $environment = [ - 'XDEBUG_MODE' => 'off', - ]; + $context = context(); + $context = $context->withEnvironment(['XDEBUG_MODE' => 'off']); if ($fix) { $command[] = '--fix'; } if ($clearCache) { $command[] = '--clear-cache'; } - run($command, environment: $environment); + run($command, context: $context); } #[AsTask(description: 'Running PHPStan')] @@ -88,10 +86,9 @@ function stan( if ($baseline) { $command[] = '--generate-baseline'; } - $environment = [ - 'XDEBUG_MODE' => 'off', - ]; - run($command, environment: $environment); + $context = context(); + $context = $context->withEnvironment(['XDEBUG_MODE' => 'off']); + run($command, context: $context); } #[AsTask(description: 'Validate Composer configuration')] @@ -99,13 +96,12 @@ function validate(): void { io()->title('Validating Composer configuration'); $command = ['composer', 'validate', '--strict']; - $environment = [ - 'XDEBUG_MODE' => 'off', - ]; - run($command, environment: $environment); + $context = context(); + $context = $context->withEnvironment(['XDEBUG_MODE' => 'off']); + run($command, context: $context); $command = ['composer', 'dump-autoload', '--optimize', '--strict-psr']; - run($command, environment: $environment); + run($command, context: $context); } /** @@ -118,10 +114,10 @@ function checkLicenses( io()->title('Checking licenses'); $allowedExceptions = []; $command = ['composer', 'licenses', '-f', 'json']; - $environment = [ - 'XDEBUG_MODE' => 'off', - ]; - $result = run($command, environment: $environment, quiet: true); + $context = context(); + $context = $context->withEnvironment(['XDEBUG_MODE' => 'off']); + $context = $context->withQuiet(); + $result = run($command, context: $context); if (! $result->isSuccessful()) { io()->error('Cannot determine licenses'); exit(1); @@ -181,10 +177,9 @@ function rector( if ($clearCache) { $command[] = '--clear-cache'; } - $environment = [ - 'XDEBUG_MODE' => 'off', - ]; - run($command, environment: $environment); + $context = context(); + $context = $context->withEnvironment(['XDEBUG_MODE' => 'off']); + run($command, context: $context); } #[AsTask(description: 'Run Rector')] @@ -192,10 +187,9 @@ function deptrac(): void { io()->title('Running Rector'); $command = ['php', 'vendor/bin/deptrac', 'analyse', '--fail-on-uncovered', '--no-cache']; - $environment = [ - 'XDEBUG_MODE' => 'off', - ]; - run($command, environment: $environment); + $context = context(); + $context = $context->withEnvironment(['XDEBUG_MODE' => 'off']); + run($command, context: $context); } #[AsTask(description: 'Run Linter')] @@ -203,8 +197,7 @@ function lint(): void { io()->title('Running Linter'); $command = ['composer', 'exec', '--', 'parallel-lint', __DIR__ . '/src/', __DIR__ . '/tests/']; - $environment = [ - 'XDEBUG_MODE' => 'off', - ]; - run($command, environment: $environment); + $context = context(); + $context = $context->withEnvironment(['XDEBUG_MODE' => 'off']); + run($command, context: $context); } diff --git a/link b/link old mode 100644 new mode 100755 diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e8ad28c..14f7bdc 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -455,11 +455,6 @@ parameters: count: 1 path: src/ImageProcessor/GDImageProcessor.php - - - message: "#^Should not use node with type \"Stmt_Echo\", please change the code\\.$#" - count: 3 - path: src/ImageProcessor/GDImageProcessor.php - - message: "#^PHPDoc tag @return with type array\\ is incompatible with native type string\\.$#" count: 1 diff --git a/phpstan.neon b/phpstan.neon index d550a2c..1dd34f5 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,8 +7,10 @@ parameters: checkGenericClassInNonGenericObjectType: true checkUninitializedProperties: true treatPhpDocTypesAsCertain: false + banned_code: + non_ignorable: false scanFiles: - vendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php includes: - vendor/phpstan/phpstan/conf/bleedingEdge.neon - - phpstan-baseline.neon + - phpstan-baseline.neon \ No newline at end of file diff --git a/src/CachingStrategy/AssetCache.php b/src/CachingStrategy/AssetCache.php index 7be0018..7e69c70 100644 --- a/src/CachingStrategy/AssetCache.php +++ b/src/CachingStrategy/AssetCache.php @@ -16,6 +16,7 @@ use Symfony\Component\Serializer\Encoder\JsonEncode; use Symfony\Component\Serializer\SerializerInterface; use function count; +use function sprintf; use const JSON_PRETTY_PRINT; use const JSON_THROW_ON_ERROR; use const JSON_UNESCAPED_SLASHES; diff --git a/src/CachingStrategy/ImageCache.php b/src/CachingStrategy/ImageCache.php index daaeffe..506add9 100644 --- a/src/CachingStrategy/ImageCache.php +++ b/src/CachingStrategy/ImageCache.php @@ -11,6 +11,7 @@ use SpomkyLabs\PwaBundle\Service\CanLogInterface; use Symfony\Component\AssetMapper\Path\PublicAssetsPathResolverInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; +use function sprintf; final class ImageCache implements HasCacheStrategiesInterface, CanLogInterface { diff --git a/src/CachingStrategy/ManifestCache.php b/src/CachingStrategy/ManifestCache.php index 87dd1dc..712e9d6 100644 --- a/src/CachingStrategy/ManifestCache.php +++ b/src/CachingStrategy/ManifestCache.php @@ -10,6 +10,7 @@ use SpomkyLabs\PwaBundle\Dto\Workbox; use SpomkyLabs\PwaBundle\Service\CanLogInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; +use function sprintf; final class ManifestCache implements HasCacheStrategiesInterface, CanLogInterface { diff --git a/src/CachingStrategy/PreloadUrlsGeneratorManager.php b/src/CachingStrategy/PreloadUrlsGeneratorManager.php index c9ac49c..b740a19 100644 --- a/src/CachingStrategy/PreloadUrlsGeneratorManager.php +++ b/src/CachingStrategy/PreloadUrlsGeneratorManager.php @@ -10,6 +10,7 @@ use SpomkyLabs\PwaBundle\Service\CanLogInterface; use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; use function array_key_exists; +use function sprintf; final class PreloadUrlsGeneratorManager implements CanLogInterface { diff --git a/src/CachingStrategy/ResourceCaches.php b/src/CachingStrategy/ResourceCaches.php index 300d803..3fe2e3d 100644 --- a/src/CachingStrategy/ResourceCaches.php +++ b/src/CachingStrategy/ResourceCaches.php @@ -20,6 +20,7 @@ use Symfony\Component\Serializer\Encoder\JsonEncode; use Symfony\Component\Serializer\SerializerInterface; use function count; +use function sprintf; use const JSON_PRETTY_PRINT; use const JSON_THROW_ON_ERROR; use const JSON_UNESCAPED_SLASHES; diff --git a/src/CachingStrategy/WorkboxCacheStrategy.php b/src/CachingStrategy/WorkboxCacheStrategy.php index 73076ec..21086f6 100644 --- a/src/CachingStrategy/WorkboxCacheStrategy.php +++ b/src/CachingStrategy/WorkboxCacheStrategy.php @@ -6,6 +6,7 @@ use SpomkyLabs\PwaBundle\WorkboxPlugin\CachePluginInterface; use function in_array; +use function sprintf; use const JSON_PRETTY_PRINT; use const JSON_THROW_ON_ERROR; use const JSON_UNESCAPED_SLASHES; diff --git a/src/Command/CreateIconsCommand.php b/src/Command/CreateIconsCommand.php index 3d822cd..16d464b 100644 --- a/src/Command/CreateIconsCommand.php +++ b/src/Command/CreateIconsCommand.php @@ -19,6 +19,7 @@ use Symfony\Component\Mime\MimeTypes; use Symfony\Component\Yaml\Yaml; use function is_string; +use function sprintf; #[AsCommand(name: 'pwa:create:icons', description: 'Generate icons for your PWA')] final class CreateIconsCommand extends Command diff --git a/src/Command/CreateScreenshotCommand.php b/src/Command/CreateScreenshotCommand.php index dcd6aa4..b4f02b3 100644 --- a/src/Command/CreateScreenshotCommand.php +++ b/src/Command/CreateScreenshotCommand.php @@ -24,6 +24,7 @@ use function assert; use function is_int; use function is_string; +use function sprintf; #[AsCommand( name: 'pwa:create:screenshot', @@ -155,7 +156,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($outputMimeType !== null) { $config['type'] = $outputMimeType; } - if ($title !== null && $title !== '') { + if ($title !== null) { $config['label'] = $title; } $io->success('Screenshot saved. You can now use it in your application configuration file.'); diff --git a/src/CompilerPass/PreloadUrlCompilerPass.php b/src/CompilerPass/PreloadUrlCompilerPass.php index 286e173..8ecbf9d 100644 --- a/src/CompilerPass/PreloadUrlCompilerPass.php +++ b/src/CompilerPass/PreloadUrlCompilerPass.php @@ -17,6 +17,7 @@ use Throwable; use function array_key_exists; use function is_string; +use function sprintf; /** * @internal diff --git a/src/ImageProcessor/Configuration.php b/src/ImageProcessor/Configuration.php index e64755f..fc4df4b 100644 --- a/src/ImageProcessor/Configuration.php +++ b/src/ImageProcessor/Configuration.php @@ -6,6 +6,7 @@ use InvalidArgumentException; use Stringable; +use function sprintf; final readonly class Configuration implements Stringable { diff --git a/src/ImageProcessor/GDImageProcessor.php b/src/ImageProcessor/GDImageProcessor.php index 4153e0c..dc24801 100644 --- a/src/ImageProcessor/GDImageProcessor.php +++ b/src/ImageProcessor/GDImageProcessor.php @@ -46,7 +46,9 @@ public function process( $pngData = ob_get_clean(); assert(is_string($pngData)); + // @phpstan-ignore-next-line echo pack('v3', 0, 1, 1); + // @phpstan-ignore-next-line echo pack( 'C4v2V2', $configuration->width, @@ -58,6 +60,7 @@ public function process( mb_strlen($pngData, '8bit'), 22 ); + // @phpstan-ignore-next-line echo $pngData; break; default: @@ -121,11 +124,10 @@ private function createMainImage(string $image, Configuration $configuration): G } /*if ($configuration->width === $configuration->height) { - $mainImage = imagescale($mainImage, $configuration->width, $configuration->height); - assert($mainImage !== false); - - return $mainImage; - }*/ + * $mainImage = imagescale($mainImage, $configuration->width, $configuration->height); + * assert($mainImage !== false); + * return $mainImage; + * }*/ $srcWidth = imagesx($mainImage); $srcHeight = imagesy($mainImage); diff --git a/src/MatchCallbackHandler/DestinationMatchCallbackHandler.php b/src/MatchCallbackHandler/DestinationMatchCallbackHandler.php index ba4ff12..d99250c 100644 --- a/src/MatchCallbackHandler/DestinationMatchCallbackHandler.php +++ b/src/MatchCallbackHandler/DestinationMatchCallbackHandler.php @@ -7,6 +7,7 @@ use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use SpomkyLabs\PwaBundle\Service\CanLogInterface; +use function sprintf; final class DestinationMatchCallbackHandler implements MatchCallbackHandlerInterface, CanLogInterface { diff --git a/src/MatchCallbackHandler/ExactPathnameMatchCallbackHandler.php b/src/MatchCallbackHandler/ExactPathnameMatchCallbackHandler.php index 050177a..a295b33 100644 --- a/src/MatchCallbackHandler/ExactPathnameMatchCallbackHandler.php +++ b/src/MatchCallbackHandler/ExactPathnameMatchCallbackHandler.php @@ -7,6 +7,7 @@ use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use SpomkyLabs\PwaBundle\Service\CanLogInterface; +use function sprintf; final class ExactPathnameMatchCallbackHandler implements MatchCallbackHandlerInterface, CanLogInterface { diff --git a/src/MatchCallbackHandler/OriginMatchCallbackHandler.php b/src/MatchCallbackHandler/OriginMatchCallbackHandler.php index 7d0eb7a..f34f943 100644 --- a/src/MatchCallbackHandler/OriginMatchCallbackHandler.php +++ b/src/MatchCallbackHandler/OriginMatchCallbackHandler.php @@ -7,6 +7,7 @@ use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use SpomkyLabs\PwaBundle\Service\CanLogInterface; +use function sprintf; final class OriginMatchCallbackHandler implements MatchCallbackHandlerInterface, CanLogInterface { diff --git a/src/MatchCallbackHandler/PathnameEndsWithMatchCallbackHandler.php b/src/MatchCallbackHandler/PathnameEndsWithMatchCallbackHandler.php index f134627..71ed16c 100644 --- a/src/MatchCallbackHandler/PathnameEndsWithMatchCallbackHandler.php +++ b/src/MatchCallbackHandler/PathnameEndsWithMatchCallbackHandler.php @@ -7,6 +7,7 @@ use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use SpomkyLabs\PwaBundle\Service\CanLogInterface; +use function sprintf; final class PathnameEndsWithMatchCallbackHandler implements MatchCallbackHandlerInterface, CanLogInterface { diff --git a/src/MatchCallbackHandler/PathnameStartsWithMatchCallbackHandler.php b/src/MatchCallbackHandler/PathnameStartsWithMatchCallbackHandler.php index b643f86..d0ed63b 100644 --- a/src/MatchCallbackHandler/PathnameStartsWithMatchCallbackHandler.php +++ b/src/MatchCallbackHandler/PathnameStartsWithMatchCallbackHandler.php @@ -7,6 +7,7 @@ use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use SpomkyLabs\PwaBundle\Service\CanLogInterface; +use function sprintf; final class PathnameStartsWithMatchCallbackHandler implements MatchCallbackHandlerInterface, CanLogInterface { diff --git a/src/MatchCallbackHandler/RouteMatchCallbackHandler.php b/src/MatchCallbackHandler/RouteMatchCallbackHandler.php index 2494790..bfc51cf 100644 --- a/src/MatchCallbackHandler/RouteMatchCallbackHandler.php +++ b/src/MatchCallbackHandler/RouteMatchCallbackHandler.php @@ -8,6 +8,7 @@ use Psr\Log\NullLogger; use SpomkyLabs\PwaBundle\Service\CanLogInterface; use Symfony\Component\Routing\RouterInterface; +use function sprintf; final class RouteMatchCallbackHandler implements MatchCallbackHandlerInterface, CanLogInterface { diff --git a/src/Normalizer/ScreenshotNormalizer.php b/src/Normalizer/ScreenshotNormalizer.php index 9efb9c1..f816a1e 100644 --- a/src/Normalizer/ScreenshotNormalizer.php +++ b/src/Normalizer/ScreenshotNormalizer.php @@ -13,6 +13,7 @@ use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use function assert; +use function sprintf; final class ScreenshotNormalizer implements NormalizerInterface, NormalizerAwareInterface { diff --git a/src/Service/FaviconsCompiler.php b/src/Service/FaviconsCompiler.php index 50e9fad..37fbb1c 100644 --- a/src/Service/FaviconsCompiler.php +++ b/src/Service/FaviconsCompiler.php @@ -15,6 +15,7 @@ use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Process; use function assert; +use function sprintf; use const PHP_EOL; final class FaviconsCompiler implements FileCompilerInterface, CanLogInterface diff --git a/src/Service/IconResolver.php b/src/Service/IconResolver.php index f13692b..07bf878 100644 --- a/src/Service/IconResolver.php +++ b/src/Service/IconResolver.php @@ -17,6 +17,7 @@ use function assert; use function count; use function is_array; +use function sprintf; final readonly class IconResolver { diff --git a/src/Service/ServiceWorkerCompiler.php b/src/Service/ServiceWorkerCompiler.php index be25c01..955fdd2 100644 --- a/src/Service/ServiceWorkerCompiler.php +++ b/src/Service/ServiceWorkerCompiler.php @@ -17,6 +17,7 @@ use function in_array; use function is_array; use function is_string; +use function sprintf; final class ServiceWorkerCompiler implements FileCompilerInterface, CanLogInterface { diff --git a/src/ServiceWorkerRule/AppendCacheStrategies.php b/src/ServiceWorkerRule/AppendCacheStrategies.php index cda6871..219a578 100644 --- a/src/ServiceWorkerRule/AppendCacheStrategies.php +++ b/src/ServiceWorkerRule/AppendCacheStrategies.php @@ -7,6 +7,7 @@ use SpomkyLabs\PwaBundle\CachingStrategy\HasCacheStrategiesInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\DependencyInjection\Attribute\TaggedIterator; +use function sprintf; use const PHP_EOL; final readonly class AppendCacheStrategies implements ServiceWorkerRuleInterface diff --git a/src/Twig/PwaRuntime.php b/src/Twig/PwaRuntime.php index 32cd7e2..484a0aa 100644 --- a/src/Twig/PwaRuntime.php +++ b/src/Twig/PwaRuntime.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Mime\MimeTypes; use function array_key_exists; +use function sprintf; use const ENT_COMPAT; use const ENT_SUBSTITUTE; use const PHP_EOL; diff --git a/src/WorkboxPlugin/BroadcastUpdatePlugin.php b/src/WorkboxPlugin/BroadcastUpdatePlugin.php index 8d5011d..33fde5a 100644 --- a/src/WorkboxPlugin/BroadcastUpdatePlugin.php +++ b/src/WorkboxPlugin/BroadcastUpdatePlugin.php @@ -4,6 +4,8 @@ namespace SpomkyLabs\PwaBundle\WorkboxPlugin; +use function sprintf; + final readonly class BroadcastUpdatePlugin implements CachePluginInterface, HasDebugInterface { private const NAME = 'BroadcastUpdatePlugin'; diff --git a/src/WorkboxPlugin/CacheableResponsePlugin.php b/src/WorkboxPlugin/CacheableResponsePlugin.php index 011d145..6cec135 100644 --- a/src/WorkboxPlugin/CacheableResponsePlugin.php +++ b/src/WorkboxPlugin/CacheableResponsePlugin.php @@ -4,6 +4,8 @@ namespace SpomkyLabs\PwaBundle\WorkboxPlugin; +use function sprintf; + final readonly class CacheableResponsePlugin implements CachePluginInterface, HasDebugInterface { private const NAME = 'CacheableResponsePlugin'; diff --git a/src/WorkboxPlugin/ExpirationPlugin.php b/src/WorkboxPlugin/ExpirationPlugin.php index 79eb733..141b913 100644 --- a/src/WorkboxPlugin/ExpirationPlugin.php +++ b/src/WorkboxPlugin/ExpirationPlugin.php @@ -4,6 +4,8 @@ namespace SpomkyLabs\PwaBundle\WorkboxPlugin; +use function sprintf; + final readonly class ExpirationPlugin implements CachePluginInterface, HasDebugInterface { private const NAME = 'ExpirationPlugin'; diff --git a/tests/Functional/AbstractPwaTestCase.php b/tests/Functional/AbstractPwaTestCase.php index 64a27b5..046c286 100644 --- a/tests/Functional/AbstractPwaTestCase.php +++ b/tests/Functional/AbstractPwaTestCase.php @@ -8,6 +8,7 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Filesystem\Filesystem; use function assert; +use function sprintf; /** * @internal diff --git a/tests/Functional/GenerateIconsCommandTest.php b/tests/Functional/GenerateIconsCommandTest.php index 77b16c6..7e2b2ee 100644 --- a/tests/Functional/GenerateIconsCommandTest.php +++ b/tests/Functional/GenerateIconsCommandTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\Attributes\Test; use Symfony\Component\Console\Tester\CommandTester; use function assert; +use function sprintf; /** * @internal diff --git a/tests/Functional/TakeScreenshotCommandTest.php b/tests/Functional/TakeScreenshotCommandTest.php index accd081..a3e2592 100644 --- a/tests/Functional/TakeScreenshotCommandTest.php +++ b/tests/Functional/TakeScreenshotCommandTest.php @@ -8,6 +8,7 @@ use PHPUnit\Framework\Attributes\Test; use Symfony\Component\Console\Tester\CommandTester; use function assert; +use function sprintf; /** * @internal diff --git a/tests/TestFilesystem.php b/tests/TestFilesystem.php index 002a7b5..70a7e02 100644 --- a/tests/TestFilesystem.php +++ b/tests/TestFilesystem.php @@ -7,6 +7,7 @@ use Symfony\Component\AssetMapper\Path\PublicAssetsFilesystemInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; use function dirname; +use function sprintf; final readonly class TestFilesystem implements PublicAssetsFilesystemInterface {