From b2fe9ee73409edb1b75f5b7590f53aa1733f0780 Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Mon, 10 Feb 2025 09:16:05 +0100 Subject: [PATCH] Add PWA controllers and refactor configuration Introduced new Stimulus controllers for handling device motion, orientation, touch events, network information, and speech synthesis for Progressive Web Apps (PWA). Enhanced configuration options, added support for monochrome icons, and implemented better event dispatch naming. Code adjustments also included stricter type handling and cleanup of dead methods. --- .github/workflows/scorecards.yml | 2 +- assets/package.json | 28 + assets/src/abstract_controller.js | 18 +- assets/src/backgroundsync-form_controller.js | 4 +- assets/src/battery_controller.js | 2 +- assets/src/connection-status_controller.js | 4 +- assets/src/device-motion_controller.js | 36 + assets/src/device-orientation_controller.js | 39 +- assets/src/geolocation_controller.js | 6 +- assets/src/network-information_controller.js | 17 + assets/src/prefetch-on-demand_controller.js | 2 +- assets/src/presentation_controller.js | 3 +- assets/src/receiver_controller.js | 32 +- assets/src/speech-synthesis_controller.js | 40 ++ assets/src/sync-broadcast_controller.js | 2 +- assets/src/touch_controller.js | 76 +++ composer.json | 10 +- phpstan-baseline.neon | 642 +++++++++++++++++- src/Dto/Favicons.php | 2 + src/ImageProcessor/Configuration.php | 9 +- src/ImageProcessor/ImagickImageProcessor.php | 53 +- src/Normalizer/ScreenshotNormalizer.php | 3 +- src/Resources/config/definition/favicons.php | 4 + src/Resources/config/definition/manifest.php | 7 + .../config/definition/utils/icons.php | 25 +- src/Service/FaviconsCompiler.php | 1 + src/Service/IconResolver.php | 5 +- 27 files changed, 987 insertions(+), 85 deletions(-) create mode 100644 assets/src/device-motion_controller.js create mode 100644 assets/src/network-information_controller.js create mode 100644 assets/src/speech-synthesis_controller.js create mode 100644 assets/src/touch_controller.js diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 531a3d1..ed4b148 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -50,7 +50,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@v4.4.3 + uses: actions/upload-artifact@v4.6.0 with: name: SARIF file path: results.sarif diff --git a/assets/package.json b/assets/package.json index 58a9e7e..9989912 100644 --- a/assets/package.json +++ b/assets/package.json @@ -33,6 +33,13 @@ "fetch": "eager", "enabled": true }, + "device-motion": { + "main": "src/device-motion_controller.js", + "name": "pwa/device-motion", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, "device-orientation": { "main": "src/device-orientation_controller.js", "name": "pwa/device-orientation", @@ -61,6 +68,13 @@ "fetch": "eager", "enabled": true }, + "network-information": { + "main": "src/network-information_controller.js", + "name": "pwa/network-information", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, "prefetch-on-demand": { "main": "src/prefetch-on-demand_controller.js", "name": "pwa/prefetch-on-demand", @@ -89,6 +103,13 @@ "fetch": "eager", "enabled": true }, + "speech-synthesis": { + "main": "src/speech-synthesis_controller.js", + "name": "pwa/speech-synthesis", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, "sync-broadcast": { "main": "src/sync-broadcast_controller.js", "name": "pwa/sync-broadcast", @@ -96,6 +117,13 @@ "fetch": "eager", "enabled": true }, + "touch": { + "main": "src/touch_controller.js", + "name": "pwa/touch", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + }, "vibration": { "main": "src/vibration_controller.js", "name": "pwa/vibration", diff --git a/assets/src/abstract_controller.js b/assets/src/abstract_controller.js index bab060b..aa37c53 100644 --- a/assets/src/abstract_controller.js +++ b/assets/src/abstract_controller.js @@ -1,10 +1,24 @@ 'use strict'; +import { getComponent } from '@symfony/ux-live-component'; import { Controller } from '@hotwired/stimulus'; -/* stimulusFetch: 'lazy' */ export default class extends Controller { + component = null; + async initialize() { + try { + this.component = await getComponent(this.element); + } catch (e) { + } + } + dispatchEvent = (name, payload) => { - this.dispatch(name, { detail: payload }); + if (payload === undefined) { + payload = {}; + } + this.dispatch(name, { detail: payload, bubbles: true }); + if (this.component) { + this.component.emit(name, payload); + } } } diff --git a/assets/src/backgroundsync-form_controller.js b/assets/src/backgroundsync-form_controller.js index 28a9238..0eae4c0 100644 --- a/assets/src/backgroundsync-form_controller.js +++ b/assets/src/backgroundsync-form_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 = { params: { type: Object, diff --git a/assets/src/battery_controller.js b/assets/src/battery_controller.js index 13e6c8d..275e4df 100644 --- a/assets/src/battery_controller.js +++ b/assets/src/battery_controller.js @@ -18,7 +18,7 @@ export default class extends AbstractController { } update = async ({counter}) => { await navigator.setAppBadge(counter); - this.dispatchEvent('badge:updated', { counter }); + this.dispatchEvent('battery:updated', { counter }); } updateChargeInfo = async (battery) => { diff --git a/assets/src/connection-status_controller.js b/assets/src/connection-status_controller.js index ba28ba3..3dbf33b 100644 --- a/assets/src/connection-status_controller.js +++ b/assets/src/connection-status_controller.js @@ -11,7 +11,7 @@ export default class extends AbstractController { }; connect = () => { - this.dispatchEvent('connect', {}); + this.dispatchEvent('connection-status:connect', {}); if (navigator.onLine) { this.statusChanged({ status: 'ONLINE', @@ -45,6 +45,6 @@ export default class extends AbstractController { this.attributeTargets.forEach((element) => { element.setAttribute('data-connection-status', data.status); }); - this.dispatchEvent('status-changed', { detail: data }); + this.dispatchEvent('connection-status:changed', { detail: data }); } } diff --git a/assets/src/device-motion_controller.js b/assets/src/device-motion_controller.js new file mode 100644 index 0000000..cc8253b --- /dev/null +++ b/assets/src/device-motion_controller.js @@ -0,0 +1,36 @@ +'use strict'; + +import AbstractController from './abstract_controller.js'; + +/* stimulusFetch: 'lazy' */ +export default class extends AbstractController { + static values = { + throttle: { type: Number, default: 1000 }, + }; + + connect() { + const throttle = (func, limit) => { + let inThrottle; + return function() { + const context = this; + if (!inThrottle) { + func.apply(context, arguments); + inThrottle = true; + setTimeout(() => (inThrottle = false), limit); + } + }; + }; + + const dispatchMotionEvent = (event) => { + this.dispatchEvent('device:motion', { + acceleration: event.acceleration, + accelerationIncludingGravity: event.accelerationIncludingGravity, + rotationRate: event.rotationRate, + interval: event.interval, + }); + }; + + const throttledDispatch = throttle(dispatchMotionEvent.bind(this), this.throttleValue); + window.addEventListener('devicemotion', throttledDispatch, true); + } +} diff --git a/assets/src/device-orientation_controller.js b/assets/src/device-orientation_controller.js index 3a90092..80c27c5 100644 --- a/assets/src/device-orientation_controller.js +++ b/assets/src/device-orientation_controller.js @@ -4,18 +4,33 @@ import AbstractController from './abstract_controller.js'; /* stimulusFetch: 'lazy' */ export default class extends AbstractController { + static values = { + throttle: { type: Number, default: 1000 }, + }; + connect() { - window.addEventListener( - 'deviceorientation', - (event) => { - this.dispatchEvent({ - absolute: event.absolute, - alpha: event.alpha, - beta: event.beta, - gamma: event.gamma - }) - }, - true - ); + const throttle = (func, limit) => { + let inThrottle; + return function() { + const context = this; + if (!inThrottle) { + func.apply(context, arguments); + inThrottle = true; + setTimeout(() => (inThrottle = false), limit); + } + }; + }; + + const dispatchOrientationEvent = (event) => { + this.dispatchEvent('device:orientation', { + absolute: event.absolute, + alpha: event.alpha, + beta: event.beta, + gamma: event.gamma, + }); + }; + + const throttledDispatch = throttle(dispatchOrientationEvent.bind(this), this.throttleValue); + window.addEventListener('deviceorientation', throttledDispatch, true); } } diff --git a/assets/src/geolocation_controller.js b/assets/src/geolocation_controller.js index 540eb50..37d111f 100644 --- a/assets/src/geolocation_controller.js +++ b/assets/src/geolocation_controller.js @@ -13,7 +13,7 @@ export default class extends AbstractController { } navigator.geolocation.getCurrentPosition( - (position) => {this.dispatchEvent('geolocation:position', {latitude: position.coords.latitude, longitude: position.coords.longitude});}, + (position) => {this.dispatchEvent('geolocation:position', {position});}, (error) => {this.dispatchEvent('geolocation:error', {error: error});}, params ); @@ -29,8 +29,8 @@ export default class extends AbstractController { } 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});}, + (position) => {this.dispatchEvent('geolocation:position', {position});}, + (error) => {this.dispatchEvent('geolocation:error', {error});}, params ); } diff --git a/assets/src/network-information_controller.js b/assets/src/network-information_controller.js new file mode 100644 index 0000000..2edba6d --- /dev/null +++ b/assets/src/network-information_controller.js @@ -0,0 +1,17 @@ +'use strict'; + +import { Controller } from '@hotwired/stimulus'; + +export default class extends Controller { + connect() { + const connection = navigator.connection; + connection.addEventListener('change', this.updateConnectionStatus); + this.updateConnectionStatus(); + } + + updateConnectionStatus = () => { + const connection = navigator.connection; + console.log({bubble: true, details: connection}); + this.dispatch('network-information:change', {bubble: true, details: {connection}}); + } +} diff --git a/assets/src/prefetch-on-demand_controller.js b/assets/src/prefetch-on-demand_controller.js index e8e7d55..d532da8 100644 --- a/assets/src/prefetch-on-demand_controller.js +++ b/assets/src/prefetch-on-demand_controller.js @@ -17,7 +17,7 @@ export default class extends AbstractController { } }); this.dispatchEvent( - result === true ?'prefetched': 'error', + result === true ?'prefetch:prefetched': 'prefetch:error', {params} ); } diff --git a/assets/src/presentation_controller.js b/assets/src/presentation_controller.js index cdccb54..35480b2 100644 --- a/assets/src/presentation_controller.js +++ b/assets/src/presentation_controller.js @@ -41,8 +41,7 @@ export default class extends AbstractController { if (!this.connection) { return; } - const {message} = params; - this.connection.send(message); + this.connection.send(JSON.stringify(params)); } terminate = () => { diff --git a/assets/src/receiver_controller.js b/assets/src/receiver_controller.js index 934d5b6..f51de75 100644 --- a/assets/src/receiver_controller.js +++ b/assets/src/receiver_controller.js @@ -6,22 +6,28 @@ import AbstractController from './abstract_controller.js'; export default class extends AbstractController { async connect() { if (!navigator.presentation.receiver) { + return; } - const connections = await navigator.presentation.receiver.connections; - connections.connections.map(connection => addConnection(connection)); - connections.addEventListener( - 'connectionavailable', + const list = await navigator.presentation.receiver.connectionList; + list.connections.map((connection) => this.addConnection(connection)); + } + + addConnection(connection) { + connection.addEventListener( + 'message', (event) => { - const connection = event.connection; - connection.addEventListener( - 'message', - (event) => this.dispatchEvent('message', {message: event.data}) - ); - connection.addEventListener( - 'close', - () => this.dispatchEvent('close') - ); + const data = JSON.parse(event.data); + this.dispatchEvent('receiver:message', {data}); } ); + + connection.addEventListener( + 'close', + (event) => this.dispatchEvent('receiver:close', { + connectionId: connection.connectionId, + reason: event.reason, + message: event.message + }) + ); } } diff --git a/assets/src/speech-synthesis_controller.js b/assets/src/speech-synthesis_controller.js new file mode 100644 index 0000000..62a16f8 --- /dev/null +++ b/assets/src/speech-synthesis_controller.js @@ -0,0 +1,40 @@ +'use strict'; + +import { Controller } from '@hotwired/stimulus'; + +export default class extends Controller { + connect() { + this.populateVoiceList(); + } + + locales = () => { + return speechSynthesis.getVoices().map((voice) => voice.lang); + } + + voices = ({params}) => { + const voices = speechSynthesis.getVoices() + .filter((voice) => params.locale ? voice.lang === params.locale : true) + .filter((voice) => params.type === 'distant' ? voice.localService === false : true) + .filter((voice) => params.type === 'local' ? voice.localService === true : true) + ; + console.log(voices); + + return voices; + } + + speak = ({params}) => { + const utterance = new SpeechSynthesisUtterance(params.text); + utterance.voice = speechSynthesis.getVoices().find((voice) => voice.name === params.voice); + utterance.lang = params.locale || 'en-US'; + utterance.rate = params.rate || 1; + utterance.pitch = params.pitch || 1; + speechSynthesis.speak(utterance); + } + + populateVoiceList = () => { + if (typeof speechSynthesis === "undefined") { + return; + } + speechSynthesis.getVoices(); + } +} diff --git a/assets/src/sync-broadcast_controller.js b/assets/src/sync-broadcast_controller.js index 19efde0..03f2099 100644 --- a/assets/src/sync-broadcast_controller.js +++ b/assets/src/sync-broadcast_controller.js @@ -30,6 +30,6 @@ export default class extends AbstractController { this.remainingTargets.forEach((element) => { element.innerHTML = data.remaining; }); - this.dispatchEvent('status-changed', { detail: data }); + this.dispatchEvent('sync-broadcast:status-changed', { detail: data }); } } diff --git a/assets/src/touch_controller.js b/assets/src/touch_controller.js new file mode 100644 index 0000000..f0b47a3 --- /dev/null +++ b/assets/src/touch_controller.js @@ -0,0 +1,76 @@ +'use strict'; + +import AbstractController from './abstract_controller.js'; + +export default class extends AbstractController { + ongoingTouches = []; + + connect() { + this.element.addEventListener("touchstart", this.onTouchStart); + this.element.addEventListener("touchmove", this.onTouchMove); + this.element.addEventListener("touchcancel", this.onTouchCancel); + this.element.addEventListener("touchend", this.onTouchEnd); + } + + copyTouch = ({ identifier, clientX, clientY, pageX, pageY, radiusX, radiusY, screenX, screenY, force, rotationAngle})=> { + return { + identifier, clientX, clientY, pageX, pageY, radiusX, radiusY, screenX, screenY, force, rotationAngle, + top: this.element.offsetTop, + left: this.element.offsetLeft, + }; + } + + onTouchEnd = (event) => { + event.preventDefault(); + const {changedTouches} = event; + for (let i = 0; i < changedTouches.length; i++) { + const idx = this.ongoingTouchIndexById(changedTouches[i].identifier); + this.ongoingTouches.splice(idx, 1); + this.dispatchEvent('touch:ended', { touch: changedTouches[i], bubbles: true }); + } + this.dispatchEvent('touch:updated', { touches: this.ongoingTouches, bubbles: true }); + } + + onTouchCancel = (event) => { + event.preventDefault(); + const {changedTouches} = event; + for (let i = 0; i < changedTouches.length; i++) { + const idx = this.ongoingTouchIndexById(changedTouches[i].identifier); + this.ongoingTouches.splice(idx, 1); + this.dispatchEvent('touch:cancelled', { touch: changedTouches[i], bubbles: true }); + } + this.dispatchEvent('touch:updated', { touches: this.ongoingTouches, bubbles: true }); + } + + onTouchMove = (event) => { + event.preventDefault(); + const {changedTouches} = event; + for (let i = 0; i < changedTouches.length; i++) { + const idx = this.ongoingTouchIndexById(changedTouches[i].identifier); + this.ongoingTouches.splice(idx, 1, this.copyTouch(changedTouches[i])) + this.dispatchEvent('touch:moved', { touch: changedTouches[i], bubbles: true }); + } + this.dispatchEvent('touch:updated', { touches: this.ongoingTouches, bubbles: true }); + } + + onTouchStart = (event) => { + event.preventDefault(); + const {changedTouches} = event; + for (let i = 0; i < changedTouches.length; i++) { + this.ongoingTouches.push(this.copyTouch(changedTouches[i])); + this.dispatchEvent('touch:started', { touch: changedTouches[i], bubbles: true }); + } + this.dispatchEvent('touch:updated', { touches: this.ongoingTouches, bubbles: true }); + } + + ongoingTouchIndexById = (idToFind) => { + for (let i = 0; i < this.ongoingTouches.length; i++) { + const id = this.ongoingTouches[i].identifier; + + if (id === idToFind) { + return i; + } + } + return -1; + } +} diff --git a/composer.json b/composer.json index 4f01d6c..dbb3649 100644 --- a/composer.json +++ b/composer.json @@ -63,16 +63,16 @@ "phpstan/phpstan-phpunit": "^1.4|^2.0", "phpstan/phpstan-strict-rules": "^1.0|^2.0", "phpstan/phpstan-symfony": "^1.4|^2.0", - "phpunit/phpunit": "^10.1|^11.0", - "rector/rector": "^1.0|^2.0", - "staabm/phpstan-todo-by": "^0.1.27|^0.2", - "struggle-for-php/sfp-phpstan-psr-log": "^0.21.0|^0.22|^0.23", + "phpunit/phpunit": "^11.0", + "rector/rector": "^1.0|^2.0-rc2", + "shipmonk/dead-code-detector": "^0.5.1|^0.6", + "staabm/phpstan-todo-by": "^0.1|^0.2", + "struggle-for-php/sfp-phpstan-psr-log": "^0.20|^0.21|^0.22|^0.23", "symfony/filesystem": "^6.4|^7.0", "symfony/framework-bundle": "^6.4|^7.0", "symfony/mime": "^6.4|^7.0", "symfony/monolog-bundle": "^3.10", "symfony/panther": "^2.1", - "symfony/phpunit-bridge": "^6.4|^7.0", "symfony/translation": "^7.0", "symfony/yaml": "^6.4|^7.0", "symplify/easy-coding-standard": "^12.0" diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9bb10b8..86209cd 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -24,6 +24,30 @@ parameters: count: 1 path: src/CachingStrategy/AssetCache.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\AssetCache\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/AssetCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\AssetCache\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/AssetCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\BackgroundSync\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/BackgroundSync.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\BackgroundSync\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/BackgroundSync.php + - message: '#^Only iterables can be unpacked, mixed given in argument \#1\.$#' identifier: argument.unpackNonIterable @@ -42,6 +66,72 @@ parameters: count: 2 path: src/CachingStrategy/FontCache.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\FontCache\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/FontCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\FontCache\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/FontCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\GoogleFontCache\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/GoogleFontCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\GoogleFontCache\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/GoogleFontCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\ImageCache\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/ImageCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\ImageCache\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/ImageCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\ManifestCache\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/ManifestCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\ManifestCache\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/ManifestCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\PreloadUrlsGeneratorManager\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/PreloadUrlsGeneratorManager.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\PreloadUrlsGeneratorManager\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/PreloadUrlsGeneratorManager.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\PreloadUrlsTagGenerator\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/PreloadUrlsTagGenerator.php + - message: '#^Only iterables can be unpacked, mixed given in argument \#1\.$#' identifier: argument.unpackNonIterable @@ -66,12 +156,30 @@ parameters: count: 1 path: src/CachingStrategy/ResourceCaches.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\ResourceCaches\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/ResourceCaches.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\ResourceCaches\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/ResourceCaches.php + - message: '#^Part \$this\-\>options\[''networkTimeoutSeconds''\] \(mixed\) of encapsed string cannot be cast to string\.$#' identifier: encapsedStringPart.nonString count: 1 path: src/CachingStrategy/WorkboxCacheStrategy.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\CachingStrategy\\WorkboxCacheStrategy\:\:getMethod$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/CachingStrategy/WorkboxCacheStrategy.php + - message: '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\.$#' identifier: foreach.nonIterable @@ -234,6 +342,66 @@ parameters: count: 1 path: src/DataCollector/PwaCollector.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getCachingStrategies$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getData$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getFavicons$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getFaviconsFiles$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getManifest$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getManifestFiles$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getServiceWorker$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getServiceWorkerFiles$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\DataCollector\\PwaCollector\:\:getWorkbox$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/DataCollector/PwaCollector.php + - message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\BackgroundSync has an uninitialized property \$forceSyncFallback\. Give it default value or assign it in the constructor\.$#' identifier: property.uninitialized @@ -348,6 +516,36 @@ parameters: count: 1 path: src/Dto/Manifest.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Manifest\:\:getCategories$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Manifest.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Manifest\:\:getDescription$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Manifest.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Manifest\:\:getName$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Manifest.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Manifest\:\:getShortName$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Manifest.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Manifest\:\:getStartUrl$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Manifest.php + - message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\PageCache extends @final class SpomkyLabs\\PwaBundle\\Dto\\ResourceCache\.$#' identifier: class.extendsFinalByPhpDoc @@ -402,6 +600,12 @@ parameters: count: 1 path: src/Dto/Screenshot.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Screenshot\:\:getLabel$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Screenshot.php + - message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\ServiceWorker has an uninitialized property \$dest\. Give it default value or assign it in the constructor\.$#' identifier: property.uninitialized @@ -438,6 +642,18 @@ parameters: count: 1 path: src/Dto/ShareTargetParameters.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\ShareTargetParameters\:\:getText$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/ShareTargetParameters.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\ShareTargetParameters\:\:getTitle$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/ShareTargetParameters.php + - message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\Shortcut has an uninitialized property \$name\. Give it default value or assign it in the constructor\.$#' identifier: property.uninitialized @@ -468,6 +684,24 @@ parameters: count: 1 path: src/Dto/Shortcut.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Shortcut\:\:getDescription$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Shortcut.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Shortcut\:\:getName$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Shortcut.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Shortcut\:\:getShortName$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Shortcut.php + - message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\Url has an uninitialized property \$path\. Give it default value or assign it in the constructor\.$#' identifier: property.uninitialized @@ -504,6 +738,24 @@ parameters: count: 1 path: src/Dto/Widget.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Widget\:\:getDescription$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Widget.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Widget\:\:getName$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Widget.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Dto\\Widget\:\:getShortName$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Dto/Widget.php + - message: '#^Class SpomkyLabs\\PwaBundle\\Dto\\Workbox has an uninitialized property \$assetCache\. Give it default value or assign it in the constructor\.$#' identifier: property.uninitialized @@ -606,18 +858,114 @@ parameters: count: 1 path: src/ImageProcessor/GDImageProcessor.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\DestinationMatchCallbackHandler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/DestinationMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\DestinationMatchCallbackHandler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/DestinationMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\ExactPathnameMatchCallbackHandler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/ExactPathnameMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\ExactPathnameMatchCallbackHandler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/ExactPathnameMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\NavigationMatchCallbackHandler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/NavigationMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\NavigationMatchCallbackHandler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/NavigationMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\OriginMatchCallbackHandler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/OriginMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\OriginMatchCallbackHandler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/OriginMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\PathnameEndsWithMatchCallbackHandler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/PathnameEndsWithMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\PathnameEndsWithMatchCallbackHandler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/PathnameEndsWithMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\PathnameStartsWithMatchCallbackHandler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/PathnameStartsWithMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\PathnameStartsWithMatchCallbackHandler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/PathnameStartsWithMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\RouteMatchCallbackHandler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/RouteMatchCallbackHandler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\MatchCallbackHandler\\RouteMatchCallbackHandler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/MatchCallbackHandler/RouteMatchCallbackHandler.php + - message: '#^PHPDoc tag @return with type array\ is incompatible with native type string\.$#' identifier: return.phpDocType count: 1 path: src/Normalizer/AssetNormalizer.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Normalizer\\AssetNormalizer\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Normalizer/AssetNormalizer.php + - message: '#^Method SpomkyLabs\\PwaBundle\\Normalizer\\IconNormalizer\:\:normalize\(\) should return array\{src\: string, sizes\?\: string, type\?\: string, purpose\?\: string\} but returns array\\.$#' identifier: return.type count: 1 path: src/Normalizer/IconNormalizer.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Normalizer\\IconNormalizer\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Normalizer/IconNormalizer.php + - message: '#^Method SpomkyLabs\\PwaBundle\\Normalizer\\ScreenshotNormalizer\:\:normalize\(\) should return array\{src\: string, sizes\?\: string, form_factor\?\: string, label\?\: string, platform\?\: string, format\?\: string\} but returns array\\.$#' identifier: return.type @@ -630,12 +978,24 @@ parameters: count: 1 path: src/Normalizer/ScreenshotNormalizer.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Normalizer\\ScreenshotNormalizer\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Normalizer/ScreenshotNormalizer.php + - message: '#^Method SpomkyLabs\\PwaBundle\\Normalizer\\ServiceWorkerNormalizer\:\:normalize\(\) should return array\{scope\?\: string, src\: string, use_cache\?\: bool\} but returns array\\.$#' identifier: return.type count: 1 path: src/Normalizer/ServiceWorkerNormalizer.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Normalizer\\UrlNormalizer\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Normalizer/UrlNormalizer.php + - message: '#^Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition\:\:children\(\)\.$#' identifier: method.notFound @@ -651,7 +1011,7 @@ parameters: - message: '#^Cannot call method booleanNode\(\) on mixed\.$#' identifier: method.nonObject - count: 2 + count: 3 path: src/Resources/config/definition/favicons.php - @@ -669,7 +1029,7 @@ parameters: - message: '#^Cannot call method defaultFalse\(\) on mixed\.$#' identifier: method.nonObject - count: 1 + count: 2 path: src/Resources/config/definition/favicons.php - @@ -687,7 +1047,7 @@ parameters: - message: '#^Cannot call method end\(\) on mixed\.$#' identifier: method.nonObject - count: 13 + count: 14 path: src/Resources/config/definition/favicons.php - @@ -699,7 +1059,7 @@ parameters: - message: '#^Cannot call method info\(\) on mixed\.$#' identifier: method.nonObject - count: 9 + count: 10 path: src/Resources/config/definition/favicons.php - @@ -822,6 +1182,12 @@ parameters: count: 1 path: src/Resources/config/definition/manifest.php + - + message: '#^Cannot call method beforeNormalization\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: src/Resources/config/definition/manifest.php + - message: '#^Cannot call method booleanNode\(\) on mixed\.$#' identifier: method.nonObject @@ -861,7 +1227,7 @@ parameters: - message: '#^Cannot call method end\(\) on mixed\.$#' identifier: method.nonObject - count: 32 + count: 33 path: src/Resources/config/definition/manifest.php - @@ -870,6 +1236,12 @@ parameters: count: 18 path: src/Resources/config/definition/manifest.php + - + message: '#^Cannot call method ifTrue\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: src/Resources/config/definition/manifest.php + - message: '#^Cannot call method info\(\) on mixed\.$#' identifier: method.nonObject @@ -900,6 +1272,24 @@ parameters: count: 2 path: src/Resources/config/definition/manifest.php + - + message: '#^Cannot call method then\(\) on mixed\.$#' + identifier: method.nonObject + count: 1 + path: src/Resources/config/definition/manifest.php + + - + message: '#^Parameter \#1 \$icons of function expandIcons expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Resources/config/definition/manifest.php + + - + message: '#^Parameter \#2 \$array of function array_key_exists expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Resources/config/definition/manifest.php + - message: '#^Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition\:\:children\(\)\.$#' identifier: method.notFound @@ -1284,6 +1674,24 @@ parameters: count: 3 path: src/Resources/config/definition/service_worker.php + - + message: '#^Function expandIcons\(\) has parameter \$icons with no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Resources/config/definition/utils/icons.php + + - + message: '#^Function expandIcons\(\) return type has no value type specified in iterable type array\.$#' + identifier: missingType.iterableValue + count: 1 + path: src/Resources/config/definition/utils/icons.php + + - + message: '#^Parameter \#2 \$array of function array_key_exists expects array, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/Resources/config/definition/utils/icons.php + - message: '#^Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition\:\:children\(\)\.$#' identifier: method.notFound @@ -1321,7 +1729,19 @@ parameters: path: src/Service/ApplicationIconCompiler.php - - message: '#^Method SpomkyLabs\\PwaBundle\\Service\\Data\:\:getData\(\) should return string but returns mixed\.$#' + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ApplicationIconCompiler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ApplicationIconCompiler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\CanLogInterface\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/CanLogInterface.php + + - + message: '#^Method SpomkyLabs\\PwaBundle\\Service\\Data\:\:getData\(\) should return string but returns Closure\|string\.$#' identifier: return.type count: 1 path: src/Service/Data.php @@ -1332,6 +1752,24 @@ parameters: count: 1 path: src/Service/Data.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\FaviconsBuilder\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/FaviconsBuilder.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\FaviconsBuilder\:\:create$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/FaviconsBuilder.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\FaviconsBuilder\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/FaviconsBuilder.php + - message: '#^Cannot call method process\(\) on SpomkyLabs\\PwaBundle\\ImageProcessor\\ImageProcessorInterface\|null\.$#' identifier: method.nonObject @@ -1344,6 +1782,18 @@ parameters: count: 1 path: src/Service/FaviconsCompiler.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\FaviconsCompiler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/FaviconsCompiler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\FaviconsCompiler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/FaviconsCompiler.php + - message: '#^Cannot access property \$sourcePath on Symfony\\Component\\AssetMapper\\MappedAsset\|null\.$#' identifier: property.nonObject @@ -1356,18 +1806,144 @@ parameters: count: 2 path: src/Service/IconResolver.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\IconResolver\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/IconResolver.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ManifestBuilder\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ManifestBuilder.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ManifestBuilder\:\:create$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ManifestBuilder.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ManifestCompiler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ManifestCompiler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ManifestCompiler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ManifestCompiler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ServiceWorkerBuilder\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ServiceWorkerBuilder.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ServiceWorkerBuilder\:\:create$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ServiceWorkerBuilder.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ServiceWorkerCompiler\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ServiceWorkerCompiler.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Service\\ServiceWorkerCompiler\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Service/ServiceWorkerCompiler.php + - message: '#^Parameter \#2 \.\.\.\$values of function sprintf expects bool\|float\|int\|string\|null, mixed given\.$#' identifier: argument.type count: 1 path: src/ServiceWorkerRule/AppendCacheStrategies.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\AppendCacheStrategies\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/AppendCacheStrategies.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\ClearCache\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/ClearCache.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\ClearCache\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/ClearCache.php + - message: '#^Strict comparison using \=\=\= between int\<1, max\> and 0 will always evaluate to false\.$#' identifier: identical.alwaysFalse count: 1 path: src/ServiceWorkerRule/OfflineFallback.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\OfflineFallback\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/OfflineFallback.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\OfflineFallback\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/OfflineFallback.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\SkipWaiting\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/SkipWaiting.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\SkipWaiting\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/SkipWaiting.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\WindowsWidgets\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/WindowsWidgets.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\WindowsWidgets\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/WindowsWidgets.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\WorkboxImport\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/WorkboxImport.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\WorkboxImport\:\:getPriority$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/WorkboxImport.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\ServiceWorkerRule\\WorkboxImport\:\:setLogger$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/ServiceWorkerRule/WorkboxImport.php + - message: '#^Cannot access offset ''dest'' on mixed\.$#' identifier: offsetAccess.nonOffsetAccessible @@ -1410,8 +1986,62 @@ parameters: count: 8 path: src/SpomkyLabsPwaBundle.php + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Twig\\PwaRuntime\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Twig/PwaRuntime.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Twig\\PwaRuntime\:\:load$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/Twig/PwaRuntime.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\WorkboxPlugin\\BackgroundSyncPlugin\:\:getDebug$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/WorkboxPlugin/BackgroundSyncPlugin.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\WorkboxPlugin\\BroadcastUpdatePlugin\:\:getDebug$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/WorkboxPlugin/BroadcastUpdatePlugin.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\WorkboxPlugin\\CacheableResponsePlugin\:\:getDebug$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/WorkboxPlugin/CacheableResponsePlugin.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\WorkboxPlugin\\ExpirationPlugin\:\:getDebug$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/WorkboxPlugin/ExpirationPlugin.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\WorkboxPlugin\\HasDebugInterface\:\:getDebug$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/WorkboxPlugin/HasDebugInterface.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\WorkboxPlugin\\RangeRequestsPlugin\:\:getDebug$#' + identifier: shipmonk.deadMethod + count: 1 + path: src/WorkboxPlugin/RangeRequestsPlugin.php + - message: '#^Unreachable statement \- code above always terminates\.$#' identifier: deadCode.unreachable count: 1 path: tests/Functional/TakeScreenshotCommandTest.php + + - + message: '#^Unused SpomkyLabs\\PwaBundle\\Tests\\TestFilesystem\:\:__construct$#' + identifier: shipmonk.deadMethod + count: 1 + path: tests/TestFilesystem.php diff --git a/src/Dto/Favicons.php b/src/Dto/Favicons.php index f99b570..bcee576 100644 --- a/src/Dto/Favicons.php +++ b/src/Dto/Favicons.php @@ -40,4 +40,6 @@ final class Favicons public null|bool $useSilhouette = null; public null|string $potrace = null; + + public bool $monochrome = false; } diff --git a/src/ImageProcessor/Configuration.php b/src/ImageProcessor/Configuration.php index fc4df4b..8871bda 100644 --- a/src/ImageProcessor/Configuration.php +++ b/src/ImageProcessor/Configuration.php @@ -17,6 +17,7 @@ public function __construct( public null|string $backgroundColor = null, public null|int $borderRadius = null, public null|int $imageScale = null, + public bool $monochrome = false, ) { if ($this->borderRadius !== null && $this->backgroundColor === null) { throw new InvalidArgumentException('The background color must be set when the border radius is set'); @@ -26,13 +27,14 @@ public function __construct( public function __toString(): string { return sprintf( - '%d%d%s%s%s%s', + '%d%d%s%s%s%s%s', $this->width, $this->height, $this->format, $this->backgroundColor ?? '', $this->borderRadius ?? '', - $this->imageScale ?? '' + $this->imageScale ?? '', + $this->monochrome ? '1' : '0', ); } @@ -43,7 +45,8 @@ public static function create( null|string $backgroundColor = null, null|int $borderRadius = null, null|int $imageScale = null, + bool $monochrome = false, ): self { - return new self($width, $height, $format, $backgroundColor, $borderRadius, $imageScale); + return new self($width, $height, $format, $backgroundColor, $borderRadius, $imageScale, $monochrome); } } diff --git a/src/ImageProcessor/ImagickImageProcessor.php b/src/ImageProcessor/ImagickImageProcessor.php index 806e554..20e6a0f 100644 --- a/src/ImageProcessor/ImagickImageProcessor.php +++ b/src/ImageProcessor/ImagickImageProcessor.php @@ -46,35 +46,25 @@ private function createMainImage(string $image, Configuration $configuration): I $mainImage->setImageBackgroundColor(new ImagickPixel('transparent')); if ($configuration->imageScale !== null) { - $width = $mainImage->getImageWidth(); - $height = $mainImage->getImageHeight(); - $newWidth = (int) ($width * $configuration->imageScale / 100); - $newHeight = (int) ($height * $configuration->imageScale / 100); - $widthCenter = (int) (-($width - $newWidth) / 2); - $heightCenter = (int) (-($height - $newHeight) / 2); - - $mainImage->scaleImage($newWidth, $newHeight); - $mainImage->extentImage($width, $height, $widthCenter, $heightCenter); + $this->resizeImageWithScale($mainImage, $configuration->imageScale); } - if ($configuration->width === $configuration->height) { - $mainImage->scaleImage($configuration->width, $configuration->height); + // Resize image with new size to best fit the configuration + $mainImage->scaleImage($configuration->width, $configuration->height, true); - return $mainImage; - } - - $mainImage->scaleImage( - min($configuration->width, $configuration->height), - min($configuration->width, $configuration->height) - ); - $mainImage->extentImage( - $configuration->width, - $configuration->height, - -($configuration->width - min($configuration->width, $configuration->height)) / 2, - -($configuration->height - min($configuration->width, $configuration->height)) / 2 + $background = new Imagick(); + $background->newImage($configuration->width, $configuration->height, new ImagickPixel('transparent')); + $background->compositeImage( + $mainImage, + Imagick::COMPOSITE_OVER, + (int) (($configuration->width - $mainImage->getImageWidth()) / 2), + (int) (($configuration->height - $mainImage->getImageHeight()) / 2) ); + if ($configuration->monochrome) { + $background->setImageType(Imagick::IMGTYPE_GRAYSCALEMATTE); + } - return $mainImage; + return $background; } private function createBackground(Configuration $configuration): Imagick @@ -111,4 +101,19 @@ private function createBackground(Configuration $configuration): Imagick return $background; } + + private function resizeImageWithScale(Imagick $image, float|int $imageScale): void + { + $imageWidth = $image->getImageWidth(); + $imageHeight = $image->getImageHeight(); + $newWidth = (int) ($imageWidth * $imageScale / 100); + $newHeight = (int) ($imageHeight * $imageScale / 100); + + $this->resizeImageWithNewSize($image, $newWidth, $newHeight); + } + + private function resizeImageWithNewSize(Imagick $image, int $newWidth, int $newHeight): void + { + $image->scaleImage($newWidth, $newHeight, true); + } } diff --git a/src/Normalizer/ScreenshotNormalizer.php b/src/Normalizer/ScreenshotNormalizer.php index d95dbf0..cedce7f 100644 --- a/src/Normalizer/ScreenshotNormalizer.php +++ b/src/Normalizer/ScreenshotNormalizer.php @@ -105,8 +105,7 @@ private function getType(?MappedAsset $asset): ?string return null; } - $mime = MimeTypes::getDefault(); - return $mime->guessMimeType($asset->sourcePath); + return MimeTypes::getDefault()->guessMimeType($asset->sourcePath); } private function getFormFactor(?int $width, ?int $height): ?string diff --git a/src/Resources/config/definition/favicons.php b/src/Resources/config/definition/favicons.php index 49bfff0..2dca1e9 100644 --- a/src/Resources/config/definition/favicons.php +++ b/src/Resources/config/definition/favicons.php @@ -53,6 +53,10 @@ 'Use only the silhouette of the icon. Applicable for macOS Safari and Windows 8+. Requires potrace to be installed.' ) ->end() + ->booleanNode('monochrome') + ->defaultFalse() + ->info('Use a monochrome icon.') + ->end() ->scalarNode('potrace') ->defaultValue('potrace') ->info('The path to the potrace binary.') diff --git a/src/Resources/config/definition/manifest.php b/src/Resources/config/definition/manifest.php index c47eb76..8743be1 100644 --- a/src/Resources/config/definition/manifest.php +++ b/src/Resources/config/definition/manifest.php @@ -19,6 +19,13 @@ $definition->rootNode() ->children() ->arrayNode('manifest') + ->beforeNormalization() + ->ifTrue(static fn (mixed $v): bool => array_key_exists('icons', $v) && is_array($v['icons'])) + ->then(static function (array $v): array { + $v['icons'] = expandIcons($v['icons'] ?? []); + return $v; + }) + ->end() ->canBeEnabled() ->children() ->scalarNode('public_url') diff --git a/src/Resources/config/definition/utils/icons.php b/src/Resources/config/definition/utils/icons.php index 83a57b6..a129d37 100644 --- a/src/Resources/config/definition/utils/icons.php +++ b/src/Resources/config/definition/utils/icons.php @@ -5,6 +5,25 @@ use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; +function expandIcons(array $icons): array +{ + $expandedIcons = []; + foreach ($icons as $icon) { + if (! array_key_exists('sizes', $icon) || ! is_array($icon['sizes'])) { + $expandedIcons[] = $icon; + continue; + } + + foreach ($icon['sizes'] as $size) { + $expandedIcon = $icon; + $expandedIcon['sizes'] = [$size]; + $expandedIcons[] = $expandedIcon; + } + } + + return $expandedIcons; +} + function getIconsNode(string $info): ArrayNodeDefinition { $treeBuilder = new TreeBuilder('icons'); @@ -17,9 +36,9 @@ function getIconsNode(string $info): ArrayNodeDefinition ->arrayPrototype() ->beforeNormalization() ->ifString() - ->then(static fn (string $v): array => [ - 'src' => $v, - ]) + ->then(static fn (string $v): array => [ + 'src' => $v, + ]) ->end() ->children() ->scalarNode('src') diff --git a/src/Service/FaviconsCompiler.php b/src/Service/FaviconsCompiler.php index 372b01e..c4742a6 100644 --- a/src/Service/FaviconsCompiler.php +++ b/src/Service/FaviconsCompiler.php @@ -232,6 +232,7 @@ public function getFiles(): iterable $this->favicons->backgroundColor, $this->favicons->borderRadius, $this->favicons->imageScale, + $this->favicons->monochrome ); $completeHash = hash('xxh128', $hash . $configuration); $filename = sprintf($size['url'], $size['width'], $size['height'], $completeHash); diff --git a/src/Service/IconResolver.php b/src/Service/IconResolver.php index 07bf878..bdff21d 100644 --- a/src/Service/IconResolver.php +++ b/src/Service/IconResolver.php @@ -68,7 +68,8 @@ public function getIcon(Icon $icon): Data $icon->format ?? $asset->publicExtension, $icon->backgroundColor, $icon->borderRadius, - $icon->imageScale + $icon->imageScale, + str_contains($icon->purpose ?? '', 'monochrome'), ); $format = $icon->format ?? $asset->publicExtension; $hash = hash( @@ -80,7 +81,7 @@ public function getIcon(Icon $icon): Data $format, $size, $icon->purpose ?? '', - $icon->type ?? '' + $icon->type ?? '', ) ); $url = sprintf('/pwa/icon-%s.%s', $hash, $format);